start_command/
detached_output.rs1use chrono::{DateTime, SecondsFormat, Utc};
3use std::fs::{self, OpenOptions};
4use std::io::{Read, Write};
5
6use crate::execution_attempt::activity_path;
7
8pub const INTERNAL_OUTPUT_FLAG: &str = "--internal-capture-detached-output";
9
10pub struct OutputObserver {
11 log: fs::File,
12 activity: String,
13 since: DateTime<Utc>,
14 prefix: Vec<u8>,
15 at_line_start: bool,
16 latest: Option<DateTime<Utc>>,
17}
18
19impl OutputObserver {
20 pub fn new(log_path: &str, number: u64, since: &str) -> std::io::Result<Self> {
21 let since = DateTime::parse_from_rfc3339(since)
22 .map_err(|error| std::io::Error::new(std::io::ErrorKind::InvalidInput, error))?
23 .with_timezone(&Utc);
24 Ok(Self {
25 log: OpenOptions::new()
26 .create(true)
27 .append(true)
28 .open(log_path)?,
29 activity: activity_path(log_path, number),
30 since,
31 prefix: Vec::with_capacity(80),
32 at_line_start: true,
33 latest: None,
34 })
35 }
36
37 pub fn write(&mut self, chunk: &[u8]) -> std::io::Result<()> {
38 self.log.write_all(chunk)?;
39 let previous = self.latest;
40 for byte in chunk {
41 if *byte == b'\n' {
42 self.at_line_start = true;
43 self.prefix.clear();
44 } else if self.at_line_start {
45 if *byte == b' ' {
46 if let Ok(text) = std::str::from_utf8(&self.prefix) {
47 if text.ends_with('Z') {
48 if let Ok(time) = DateTime::parse_from_rfc3339(text) {
49 let time = time.with_timezone(&Utc);
50 if time >= self.since && self.latest.is_none_or(|last| time > last)
51 {
52 self.latest = Some(time);
53 }
54 }
55 }
56 }
57 self.at_line_start = false;
58 } else if self.prefix.len() < 80 {
59 self.prefix.push(*byte);
60 } else {
61 self.at_line_start = false;
62 }
63 }
64 }
65 if self.latest != previous {
66 if let Some(time) = self.latest {
67 let temporary = format!("{}.{}.tmp", self.activity, std::process::id());
68 fs::write(
69 &temporary,
70 time.to_rfc3339_opts(SecondsFormat::AutoSi, true),
71 )?;
72 #[cfg(windows)]
73 let _ = fs::remove_file(&self.activity);
74 fs::rename(temporary, &self.activity)?;
75 }
76 }
77 Ok(())
78 }
79}
80
81pub fn run_internal_output(args: &[String]) {
82 let [log_path, number, since] = args else {
83 return;
84 };
85 let Ok(number) = number.parse() else {
86 return;
87 };
88 let Ok(mut observer) = OutputObserver::new(log_path, number, since) else {
89 return;
90 };
91 let mut input = std::io::stdin().lock();
92 let mut buffer = [0u8; 8192];
93 while let Ok(length) = input.read(&mut buffer) {
94 if length == 0 || observer.write(&buffer[..length]).is_err() {
95 break;
96 }
97 }
98}