Skip to main content

crabshell_services/
process.rs

1//! Programs a config runs, as Quickshell's `Process`: what they print,
2//! whole or line by line, and how they ended.
3
4#[cfg(test)]
5mod tests;
6
7use std::cell::RefCell;
8use std::ffi::OsStr;
9use std::io::{ErrorKind, PipeReader, PipeWriter, Read, Write};
10use std::os::fd::AsFd;
11use std::os::unix::process::CommandExt;
12use std::panic::{self, AssertUnwindSafe, Location};
13use std::process::{Child, Command, ExitStatus, Stdio};
14use std::rc::{Rc, Weak};
15use std::sync::Once;
16use std::time::Instant;
17
18use calloop::timer::TimeoutAction;
19use calloop::{Interest, RegistrationToken};
20use crabshell_reactive::{ReadSignal, Signal, untrack};
21use crabshell_ui::Cx;
22use rustix::process::{Pid, PidfdFlags, Signal as Kill, kill_process_group, pidfd_open};
23
24use crate::lines::{Lines, decode, panic_message};
25use crate::{Shared, spawn};
26
27/// The most of each output kept in [`Process::stdout`] and
28/// [`Process::stderr`]. Lines still reach `on_line` past it.
29pub(crate) const MAX_OUTPUT: usize = 1024 * 1024;
30
31/// The most bytes left to write to a program's input before it is closed,
32/// so a program that reads nothing can't grow the shell without end.
33pub(crate) const MAX_INPUT: usize = 4 * 1024 * 1024;
34
35/// What `cx.process` and `cx.exec_detached` need from the shell: the loop.
36pub(crate) struct Processes {
37  shared: Rc<Shared>,
38  /// Every `cx.process`, so the shell can stop them all before it exits.
39  live: RefCell<Vec<Weak<Inner>>>,
40}
41
42impl Processes {
43  pub(crate) fn new(shared: Rc<Shared>) -> Processes {
44    Processes {
45      shared,
46      live: RefCell::default(),
47    }
48  }
49}
50
51/// Stops every program `cx.process` started, as the shell does before it
52/// exits: `exit` skips the drops that would, and each program has a
53/// process group of its own, which nothing else signals.
54pub(crate) fn stop_all(cx: &Cx) {
55  let Some(processes) = cx.service::<Processes>() else {
56    return;
57  };
58  let live: Vec<Rc<Inner>> = processes
59    .live
60    .borrow()
61    .iter()
62    .filter_map(Weak::upgrade)
63    .collect();
64  for inner in live {
65    inner.stop();
66  }
67}
68
69/// What `cx.process` and `cx.exec_detached` run: a [`Command`], or the
70/// program and its arguments as a list, such as `["sleep", "30"]`, as
71/// Quickshell's `command` is.
72pub trait IntoCommand {
73  /// The command, or `None` for an empty list.
74  fn into_command(self) -> Option<Command>;
75}
76
77impl IntoCommand for Command {
78  fn into_command(self) -> Option<Command> {
79    Some(self)
80  }
81}
82
83impl<S: AsRef<OsStr>> IntoCommand for Vec<S> {
84  fn into_command(self) -> Option<Command> {
85    let mut words = self.into_iter();
86    let mut command = Command::new(words.next()?);
87    command.args(words);
88    Some(command)
89  }
90}
91
92impl<S: AsRef<OsStr>, const N: usize> IntoCommand for [S; N] {
93  fn into_command(self) -> Option<Command> {
94    Vec::from(self).into_command()
95  }
96}
97
98/// A program the shell runs, from `cx.process(command)`. A clone is cheap
99/// and shares it.
100///
101/// It starts on the loop's next turn, so `on_line` and `with_stdin` set
102/// right after `cx.process` apply to it. It runs in a process group of its
103/// own, and that group gets `SIGTERM` when the context that started it
104/// goes or the command starts again. Nothing polls: the shell wakes when
105/// the program prints, takes input or ends.
106#[derive(Clone)]
107pub struct Process {
108  inner: Rc<Inner>,
109}
110
111struct Inner {
112  /// `None` when the shell provided no services.
113  processes: Option<Rc<Shared>>,
114  /// These live under the starting context's owner.
115  running: Signal<bool>,
116  status: Signal<Option<ExitStatus>>,
117  stdout: Signal<String>,
118  stderr: Signal<String>,
119  /// Read by the effect that builds the command, so bumping it starts
120  /// the command again.
121  restarts: Signal<u64>,
122  state: RefCell<State>,
123}
124
125#[derive(Default)]
126struct State {
127  /// The command to start at the loop's next turn, and the timer that
128  /// will.
129  pending: Option<Command>,
130  starter: Option<RegistrationToken>,
131  run: Option<Run>,
132  keep_stdin: bool,
133  /// Written before the program started, for its input.
134  input: Vec<u8>,
135  /// The config closed the input: it closes once what was written is
136  /// sent.
137  closing: bool,
138  /// A run ended, failed to start or was killed before it started, and
139  /// nothing is to start: writes and closes have no run to wait for.
140  ran: bool,
141  on_line: Option<OnLine>,
142  /// Which method set `on_line` and where, for the log if it panics.
143  at: Option<(&'static str, &'static Location<'static>)>,
144  /// What the run printed on its standard output and wasn't passed on
145  /// yet. Split at a newline until a handler says otherwise, so one set
146  /// late still gets the start of its first line.
147  lines: Lines,
148  /// A next-turn delivery is on its way, and passes on all that waits.
149  later: bool,
150}
151
152/// What a config does with each line.
153type OnLine = Rc<dyn Fn(&str)>;
154
155/// The program running now.
156struct Run {
157  child: Child,
158  program: String,
159  exit: Option<RegistrationToken>,
160  out: Output,
161  err: Output,
162  stdin: Option<PipeWriter>,
163  outbox: Vec<u8>,
164  writer: Option<RegistrationToken>,
165}
166
167/// One of a program's outputs, read as it comes.
168#[derive(Default)]
169struct Output {
170  stream: Option<(PipeReader, RegistrationToken)>,
171  /// The start of a character a read cut off.
172  partial: Vec<u8>,
173  /// How much is in the signal, and whether the cap was logged.
174  kept: usize,
175  capped: bool,
176}
177
178#[derive(Clone, Copy, PartialEq)]
179enum Which {
180  Out,
181  Err,
182}
183
184/// `cx.process(command)`.
185#[track_caller]
186pub(crate) fn process<C: IntoCommand>(cx: &Cx, command: impl Fn() -> C + 'static) -> Process {
187  let at = Location::caller();
188  let service = cx.service::<Processes>();
189  let processes = service
190    .as_ref()
191    .map(|processes| Rc::clone(&processes.shared));
192  if processes.is_none() {
193    static LOGGED: Once = Once::new();
194    LOGGED.call_once(|| {
195      tracing::warn!("cx.process() needs the services the shell provides; nothing runs");
196    });
197  }
198  let inner = Rc::new(Inner {
199    processes,
200    // Running from the start: the effect below starts it at the next
201    // flush.
202    running: cx.signal(true),
203    status: cx.signal(None),
204    stdout: cx.signal(String::new()),
205    stderr: cx.signal(String::new()),
206    restarts: cx.signal(0),
207    state: RefCell::default(),
208  });
209  // Dropped with the starting context's owner, which stops the program.
210  cx.owner().signal(StopOnDrop(Rc::clone(&inner)));
211  if let Some(service) = service {
212    let mut live = service.live.borrow_mut();
213    live.retain(|inner| inner.strong_count() > 0);
214    live.push(Rc::downgrade(&inner));
215  }
216  let weak = Rc::downgrade(&inner);
217  let restarts = inner.restarts;
218  // The command is the config's: built in an effect, so it is built again
219  // when a signal it read changes. A panic stops the run before and is
220  // logged; the next change tries again.
221  cx.effect(move || {
222    restarts.get();
223    let built = panic::catch_unwind(AssertUnwindSafe(|| command().into_command()));
224    let Some(inner) = weak.upgrade() else {
225      return;
226    };
227    match built {
228      Ok(next) => untrack(|| inner.start(next)),
229      Err(payload) => {
230        let message = panic_message(payload.as_ref());
231        tracing::error!("cx.process (called at {at}) panicked building its command: {message}");
232        untrack(|| inner.cancel());
233      }
234    }
235  });
236  Process { inner }
237}
238
239/// `cx.exec_detached(command)`.
240pub(crate) fn exec_detached(cx: &Cx, command: impl IntoCommand) {
241  let Some(processes) = cx.service::<Processes>() else {
242    tracing::warn!("cx.exec_detached() needs the services the shell provides; nothing runs");
243    return;
244  };
245  let Some(mut command) = command.into_command() else {
246    return tracing::warn!("cx.exec_detached() got an empty command; nothing runs");
247  };
248  // Logged there.
249  let _ = spawn::spawn(&processes.shared, &mut command);
250}
251
252/// Stops the program when the starting context's owner drops it.
253struct StopOnDrop(Rc<Inner>);
254
255impl Drop for StopOnDrop {
256  fn drop(&mut self) {
257    self.0.stop();
258  }
259}
260
261impl Process {
262  /// `true` from `cx.process` or a restart until the program ends or
263  /// can't start.
264  pub fn running(&self) -> ReadSignal<bool> {
265    self.inner.running.read_only()
266  }
267
268  /// How the last run ended: `None` while it runs, or if it couldn't
269  /// start.
270  pub fn exit_status(&self) -> ReadSignal<Option<ExitStatus>> {
271    self.inner.status.read_only()
272  }
273
274  /// Everything it printed on its standard output this run, up to 1 MiB,
275  /// decoded as UTF-8 with bad bytes replaced. Complete once
276  /// [`exit_status`](Self::exit_status) is set.
277  pub fn stdout(&self) -> ReadSignal<String> {
278    self.inner.stdout.read_only()
279  }
280
281  /// Like [`stdout`](Self::stdout), for its standard error.
282  pub fn stderr(&self) -> ReadSignal<String> {
283    self.inner.stderr.read_only()
284  }
285
286  /// Calls `on_line` with each line of its standard output, without its
287  /// `\n`, decoded as UTF-8 with bad bytes replaced, as it comes. What is
288  /// left when it ends counts as a last line. A line over 1 MiB is
289  /// dropped and logged.
290  ///
291  /// It runs on the main thread. If it panics, that is logged with the
292  /// program and where `on_line` was called, and it isn't called again.
293  #[track_caller]
294  pub fn on_line(self, on_line: impl Fn(&str) + 'static) -> Process {
295    self.handle("on_line", "\n", Rc::new(on_line))
296  }
297
298  /// Like [`on_line`](Self::on_line), with lines that end at `marker`
299  /// instead of `\n`, such as `"\0"` for `find -print0`, as Quickshell's
300  /// `SplitParser`. An empty `marker` passes on each read as it comes, up
301  /// to 64 KiB, keeping a character the read cut off for the next.
302  ///
303  /// It replaces `on_line`, and everything not passed on yet is split at
304  /// the new marker: set from a handler, from the line after the one it
305  /// was given; set elsewhere, on the loop's next turn.
306  #[track_caller]
307  pub fn on_split(self, marker: &str, on_split: impl Fn(&str) + 'static) -> Process {
308    self.handle("on_split", marker, Rc::new(on_split))
309  }
310
311  #[track_caller]
312  fn handle(self, name: &'static str, marker: &str, handler: OnLine) -> Process {
313    let mut state = self.inner.state.borrow_mut();
314    state.on_line = Some(handler);
315    state.at = Some((name, Location::caller()));
316    state.lines.set_marker(marker);
317    let waiting = state.lines.waiting();
318    drop(state);
319    if waiting {
320      self.inner.deliver_later();
321    }
322    self
323  }
324
325  /// Gives the program an input that [`write`](Self::write) feeds, open
326  /// until [`close_stdin`](Self::close_stdin). Without it the program's
327  /// input is empty. It applies from the next start, so call it right
328  /// after `cx.process`.
329  pub fn with_stdin(self) -> Process {
330    self.inner.state.borrow_mut().keep_stdin = true;
331    self
332  }
333
334  /// Sends `data` to the program's input, as it takes it; written before
335  /// the program started, it waits for the start. Without
336  /// [`with_stdin`](Self::with_stdin), or once the input is closed, `data`
337  /// is dropped and that is logged. If more than 4 MiB wait because the
338  /// program reads nothing, its input is closed.
339  pub fn write(&self, data: impl AsRef<[u8]>) {
340    self.inner.write(data.as_ref());
341  }
342
343  /// Closes the program's input once what was written is sent, so it
344  /// reads the end of it.
345  pub fn close_stdin(&self) {
346    {
347      let mut state = self.inner.state.borrow_mut();
348      // Nothing runs or is to start: the next run's input starts open.
349      if state.run.is_none() && state.ran {
350        return;
351      }
352      state.closing = true;
353    }
354    self.inner.flush();
355  }
356
357  /// Sends `SIGTERM` to the program's process group. Its exit status
358  /// comes when it ends.
359  /// Before it started, it doesn't start.
360  pub fn kill(&self) {
361    if let Some(run) = &self.inner.state.borrow().run {
362      return terminate(&run.child);
363    }
364    self.inner.cancel();
365  }
366
367  /// Stops the program if it runs and starts the command again, as when
368  /// a signal the command read changed.
369  pub fn restart(&self) {
370    self.inner.restarts.try_update(|count| *count += 1);
371  }
372}
373
374/// Sends `SIGTERM` to `child`'s process group.
375fn terminate(child: &Child) {
376  // The group is the child's own, from `process_group(0)`.
377  if let Err(error) = kill_process_group(Pid::from_child(child), Kill::TERM) {
378    tracing::debug!(%error, "couldn't stop a program; it may have ended");
379  }
380}
381
382impl Inner {
383  fn shared(&self) -> Option<&Rc<Shared>> {
384    self.processes.as_ref()
385  }
386
387  fn event_loop(&self) -> Option<Box<dyn crate::EventLoop>> {
388    self.shared()?.event_loop()
389  }
390
391  /// Stops the program running now, if one is, and starts `command` at
392  /// the loop's next turn. An empty command is logged and runs nothing.
393  fn start(self: &Rc<Self>, command: Option<Command>) {
394    self.stop_run();
395    let Some(command) = command else {
396      tracing::warn!("cx.process() got an empty command; nothing runs");
397      set(self.running, false);
398      return;
399    };
400    let Some(event_loop) = self.event_loop() else {
401      return;
402    };
403    let mut state = self.state.borrow_mut();
404    state.pending = Some(command);
405    state.ran = false;
406    if state.starter.is_none() {
407      let inner = Rc::clone(self);
408      state.starter = event_loop.insert_timer(
409        Instant::now(),
410        Box::new(move || {
411          inner.spawn_pending();
412          TimeoutAction::Drop
413        }),
414      );
415    }
416    drop(state);
417    set(self.running, true);
418    set(self.status, None);
419    set(self.stdout, String::new());
420    set(self.stderr, String::new());
421  }
422
423  fn spawn_pending(self: &Rc<Self>) {
424    let Some(event_loop) = self.event_loop() else {
425      return;
426    };
427    let (command, keep_stdin, input) = {
428      let mut state = self.state.borrow_mut();
429      state.starter = None;
430      let input = std::mem::take(&mut state.input);
431      (state.pending.take(), state.keep_stdin, input)
432    };
433    let Some(mut command) = command else {
434      return;
435    };
436    let program = command.get_program().to_string_lossy().into_owned();
437    let spawned = spawn_piped(&mut command, keep_stdin);
438    // The command holds the program's ends of the pipes. They must close
439    // here, or its outputs would never end.
440    drop(command);
441    let (child, out, err, stdin) = match spawned {
442      Ok(spawned) => spawned,
443      Err(error) => {
444        tracing::warn!(program, %error, "couldn't start the program");
445        let mut state = self.state.borrow_mut();
446        state.ran = true;
447        state.closing = false;
448        drop(state);
449        set(self.running, false);
450        return;
451      }
452    };
453    let mut run = Run {
454      child,
455      program,
456      exit: None,
457      out: Output::default(),
458      err: Output::default(),
459      stdin,
460      outbox: input,
461      writer: None,
462    };
463    self.state.borrow_mut().lines.reset();
464    for (which, stream) in [(Which::Out, out), (Which::Err, err)] {
465      let Ok(fd) = stream.as_fd().try_clone_to_owned() else {
466        continue;
467      };
468      let inner = Rc::clone(self);
469      let read = Box::new(move || inner.on_output(which));
470      if let Some(token) = event_loop.insert_fd(fd, Interest::READ, read) {
471        run.output(which).stream = Some((stream, token));
472      }
473    }
474    match pidfd_open(Pid::from_child(&run.child), PidfdFlags::NONBLOCK) {
475      Ok(fd) => {
476        let inner = Rc::clone(self);
477        let ended = Box::new(move || inner.on_exit());
478        run.exit = event_loop.insert_fd(fd, Interest::READ, ended);
479      }
480      Err(error) => tracing::warn!(%error, "couldn't follow a program to its end"),
481    }
482    self.state.borrow_mut().run = Some(run);
483    // Whatever was written before it started.
484    self.flush();
485  }
486
487  /// Reads what the program printed on one output.
488  fn on_output(&self, which: Which) {
489    let mut chunk = [0; 64 * 1024];
490    let mut state = self.state.borrow_mut();
491    let State { run, lines, .. } = &mut *state;
492    let Some(run) = run.as_mut() else {
493      return;
494    };
495    let Some((stream, _)) = run.output(which).stream.as_mut() else {
496      return;
497    };
498    let read = match stream.read(&mut chunk) {
499      Ok(read) => read,
500      Err(error) if matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::Interrupted) => {
501        return;
502      }
503      Err(_) => 0,
504    };
505    let text = run.take(which, &chunk[..read]);
506    if which == Which::Out {
507      lines.append(&chunk[..read]);
508    }
509    let program = run.program.clone();
510    if read == 0 {
511      // It closed this output; its exit is still to come.
512      let token = run.output(which).stream.take().map(|(_, token)| token);
513      if let (Some(token), Some(event_loop)) = (token, self.event_loop()) {
514        event_loop.remove(token);
515      }
516    }
517    drop(state);
518    self.show(which, text);
519    self.deliver(false, &program);
520  }
521
522  /// The program ended: reads what is left of its outputs, then shows how
523  /// it ended.
524  fn on_exit(&self) {
525    let mut state = self.state.borrow_mut();
526    let Some(run) = state.run.as_mut() else {
527      return;
528    };
529    let status = match run.child.try_wait() {
530      Ok(Some(status)) => Some(status),
531      Ok(None) => return,
532      // Reaped behind the shell's back: its end is all there is to know.
533      Err(error) => {
534        tracing::warn!(%error, program = run.program, "lost track of the program");
535        None
536      }
537    };
538    let mut run = state.run.take().expect("invariant: matched just above");
539    // The next run's input starts open.
540    state.closing = false;
541    state.ran = true;
542    drop(state);
543    // What it printed before it ended is in the sockets now. A program it
544    // started may hold them open, so read only what is there.
545    // A program it started that keeps printing would keep this going, so
546    // it stops after what the signal keeps.
547    let mut chunk = [0; 64 * 1024];
548    for which in [Which::Out, Which::Err] {
549      let mut budget = MAX_OUTPUT;
550      while let Some((stream, _)) = run.output(which).stream.as_mut()
551        && budget > 0
552      {
553        let Ok(read @ 1..) = stream.read(&mut chunk) else {
554          break;
555        };
556        budget = budget.saturating_sub(read);
557        let text = run.take(which, &chunk[..read]);
558        if which == Which::Out {
559          self.state.borrow_mut().lines.append(&chunk[..read]);
560        }
561        self.show(which, text);
562        self.deliver(false, &run.program);
563      }
564      let text = run.take(which, &[]);
565      self.show(which, text);
566    }
567    self.deliver(true, &run.program);
568    self.remove_sources(&mut run);
569    set(self.status, status);
570    set(self.running, false);
571  }
572
573  /// Adds `text` to the output's signal.
574  fn show(&self, which: Which, text: String) {
575    if text.is_empty() {
576      return;
577    }
578    let signal = match which {
579      Which::Out => self.stdout,
580      Which::Err => self.stderr,
581    };
582    signal.try_update(|all| all.push_str(&text));
583  }
584
585  /// Passes on what waits at the loop's next turn, as a marker set outside
586  /// a handler asks.
587  fn deliver_later(self: &Rc<Self>) {
588    let Some(event_loop) = self.event_loop() else {
589      return;
590    };
591    if std::mem::replace(&mut self.state.borrow_mut().later, true) {
592      return;
593    }
594    let inner = Rc::clone(self);
595    let later = move || {
596      inner.state.borrow_mut().later = false;
597      let program = inner
598        .state
599        .borrow()
600        .run
601        .as_ref()
602        .map(|run| run.program.clone());
603      if let Some(program) = program {
604        inner.deliver(false, &program);
605      }
606      TimeoutAction::Drop
607    };
608    event_loop.insert_timer(Instant::now(), Box::new(later));
609  }
610
611  /// Passes each whole line waiting to the handler set at the time, one at
612  /// a time with nothing borrowed, so one that sets another marker has the
613  /// rest split at it. Once the program is `done`, what is left goes last.
614  fn deliver(&self, done: bool, program: &str) {
615    loop {
616      let (line, on_line, at) = {
617        let mut state = self.state.borrow_mut();
618        let line = match state.lines.next(&program) {
619          Some(line) => line,
620          None if done => match state.lines.rest(&program) {
621            Some(line) => line,
622            None => return,
623          },
624          None => return,
625        };
626        (line, state.on_line.clone(), state.at)
627      };
628      let Some(on_line) = on_line else {
629        continue;
630      };
631      if let Err(payload) = panic::catch_unwind(AssertUnwindSafe(|| on_line(&line))) {
632        let (name, at) = at.map_or(("on_line", String::new()), |(name, at)| {
633          (name, format!(" (called at {at})"))
634        });
635        let message = panic_message(payload.as_ref());
636        tracing::error!("process {program} {name}{at} panicked: {message}");
637        let mut state = self.state.borrow_mut();
638        // Unless it set a new one meanwhile.
639        if state
640          .on_line
641          .as_ref()
642          .is_some_and(|now| Rc::ptr_eq(now, &on_line))
643        {
644          state.on_line = None;
645        }
646      }
647    }
648  }
649
650  fn write(self: &Rc<Self>, data: &[u8]) {
651    let mut state = self.state.borrow_mut();
652    if state.closing {
653      return tracing::warn!("the program's input is closed, so a write was dropped");
654    }
655    // Not started yet, so kept for the start.
656    let waiting = state.keep_stdin && state.run.is_none() && !state.ran;
657    match state.run.as_mut() {
658      Some(run) if run.stdin.is_some() => run.outbox.extend_from_slice(data),
659      _ if waiting => {
660        if state.input.len() + data.len() > MAX_INPUT {
661          return tracing::warn!("over 4 MiB written before the program started; dropped");
662        }
663        return state.input.extend_from_slice(data);
664      }
665      _ => return tracing::warn!("the program takes no input now, so a write was dropped"),
666    }
667    drop(state);
668    self.flush();
669  }
670
671  /// Writes what the program's input takes, and waits for room for the
672  /// rest.
673  fn flush(self: &Rc<Self>) {
674    let Some(event_loop) = self.event_loop() else {
675      return;
676    };
677    let mut state = self.state.borrow_mut();
678    let closing = state.closing;
679    let Some(run) = state.run.as_mut() else {
680      return;
681    };
682    let Some(stdin) = run.stdin.as_mut() else {
683      return;
684    };
685    let mut broken = false;
686    while !run.outbox.is_empty() {
687      match stdin.write(&run.outbox) {
688        Ok(written) => {
689          run.outbox.drain(..written);
690        }
691        Err(error) if error.kind() == ErrorKind::Interrupted => {}
692        Err(error) if error.kind() == ErrorKind::WouldBlock => break,
693        Err(_) => {
694          broken = true;
695          break;
696        }
697      }
698    }
699    if run.outbox.len() > MAX_INPUT {
700      let program = &run.program;
701      tracing::warn!(
702        program,
703        "the program reads nothing it is sent; closing its input"
704      );
705      broken = true;
706    }
707    if broken {
708      drop(state);
709      return self.drop_stdin();
710    }
711    if run.outbox.is_empty() {
712      if let Some(writer) = run.writer.take() {
713        event_loop.remove(writer);
714      }
715      if closing {
716        // All sent: the program reads the end.
717        run.stdin = None;
718      }
719    } else if run.writer.is_none()
720      && let Ok(fd) = stdin.as_fd().try_clone_to_owned()
721    {
722      let inner = Rc::clone(self);
723      run.writer = event_loop.insert_fd(fd, Interest::WRITE, Box::new(move || inner.flush()));
724    }
725  }
726
727  /// Closes the program's input now, dropping what wasn't sent.
728  fn drop_stdin(&self) {
729    let writer = {
730      let mut state = self.state.borrow_mut();
731      state.input.clear();
732      let Some(run) = state.run.as_mut() else {
733        return;
734      };
735      run.stdin = None;
736      run.outbox.clear();
737      run.writer.take()
738    };
739    if let (Some(writer), Some(event_loop)) = (writer, self.event_loop()) {
740      event_loop.remove(writer);
741    }
742  }
743
744  /// Stops the program running now, if one is: `SIGTERM` to its group,
745  /// its outputs closed, and it is left to be reaped.
746  fn stop_run(&self) {
747    let run = {
748      let mut state = self.state.borrow_mut();
749      let run = state.run.take();
750      // The next run's input starts open.
751      if run.is_some() {
752        state.closing = false;
753      }
754      run
755    };
756    let Some(mut run) = run else {
757      return;
758    };
759    // Its lines aren't passed on after it.
760    self.state.borrow_mut().lines.reset();
761    terminate(&run.child);
762    self.remove_sources(&mut run);
763    if let Some(shared) = &self.processes {
764      spawn::reap(shared, run.child, run.program);
765    }
766  }
767
768  /// Stops everything, for good, as the starting context went.
769  fn stop(&self) {
770    let (starter, on_line) = {
771      let mut state = self.state.borrow_mut();
772      state.pending = None;
773      (state.starter.take(), state.on_line.take())
774    };
775    // Dropped outside the borrow: it may hold this process, and its drop
776    // may use it.
777    drop(on_line);
778    if let (Some(starter), Some(event_loop)) = (starter, self.event_loop()) {
779      event_loop.remove(starter);
780    }
781    self.stop_run();
782  }
783
784  /// Stops the run and drops a start still to come, as when the command
785  /// panicked or the config killed it before it started.
786  fn cancel(&self) {
787    let starter = {
788      let mut state = self.state.borrow_mut();
789      state.pending = None;
790      state.ran = true;
791      state.closing = false;
792      state.starter.take()
793    };
794    if let (Some(starter), Some(event_loop)) = (starter, self.event_loop()) {
795      event_loop.remove(starter);
796    }
797    self.stop_run();
798    let ran = self.state.borrow().run.is_none();
799    if ran {
800      set(self.running, false);
801    }
802  }
803
804  fn remove_sources(&self, run: &mut Run) {
805    let tokens = [
806      run.exit.take(),
807      run.writer.take(),
808      run.out.stream.take().map(|(_, token)| token),
809      run.err.stream.take().map(|(_, token)| token),
810    ];
811    run.stdin = None;
812    if let Some(event_loop) = self.event_loop() {
813      for token in tokens.into_iter().flatten() {
814        event_loop.remove(token);
815      }
816    }
817  }
818}
819
820/// The program's outputs, and its input if wanted, through pipes, whose
821/// ends here are non-blocking. Pipes, not socket pairs: a program can
822/// open `/dev/stdout` on a pipe.
823#[allow(
824  clippy::type_complexity,
825  reason = "the four parts of one spawn, used once"
826)]
827fn spawn_piped(
828  command: &mut Command,
829  keep_stdin: bool,
830) -> std::io::Result<(Child, PipeReader, PipeReader, Option<PipeWriter>)> {
831  let (out, child_out) = std::io::pipe()?;
832  let (err, child_err) = std::io::pipe()?;
833  rustix::io::ioctl_fionbio(&out, true)?;
834  rustix::io::ioctl_fionbio(&err, true)?;
835  let (stdin, child_in) = if keep_stdin {
836    let (theirs, ours) = std::io::pipe()?;
837    rustix::io::ioctl_fionbio(&ours, true)?;
838    (Some(ours), Stdio::from(theirs))
839  } else {
840    (None, Stdio::null())
841  };
842  spawn::untune(command);
843  let child = command
844    .stdout(child_out)
845    .stderr(child_err)
846    .stdin(child_in)
847    .process_group(0)
848    .spawn()?;
849  Ok((child, out, err, stdin))
850}
851
852impl Run {
853  fn output(&mut self, which: Which) -> &mut Output {
854    match which {
855      Which::Out => &mut self.out,
856      Which::Err => &mut self.err,
857    }
858  }
859
860  /// `bytes` read from one output: the text to add to its signal. Empty
861  /// `bytes` means the output ended, which lets a cut-off character go.
862  fn take(&mut self, which: Which, bytes: &[u8]) -> String {
863    let program = self.program.clone();
864    let output = self.output(which);
865    output.partial.extend_from_slice(bytes);
866    let mut text = if bytes.is_empty() {
867      String::from_utf8_lossy(&std::mem::take(&mut output.partial)).into_owned()
868    } else {
869      decode(&mut output.partial)
870    };
871    // Once a character didn't fit, nothing after it is kept either.
872    if output.capped {
873      text.clear();
874    }
875    let room = MAX_OUTPUT.saturating_sub(output.kept);
876    if text.len() > room {
877      if !std::mem::replace(&mut output.capped, true) {
878        tracing::warn!(program, "a program printed over 1 MiB; the rest isn't kept");
879      }
880      let mut end = room;
881      while !text.is_char_boundary(end) {
882        end -= 1;
883      }
884      text.truncate(end);
885    }
886    output.kept += text.len();
887    text
888  }
889}
890
891/// Sets `signal` unless it holds `value` or is gone. The read is
892/// untracked, so an effect that starts a process doesn't run again for it.
893fn set<T: PartialEq + 'static>(signal: Signal<T>, value: T) {
894  if untrack(|| signal.try_with(|old| *old != value)) == Some(true) {
895    signal.try_set(value);
896  }
897}