1use log::debug;
2use std::fs;
3use std::fs::File;
4use std::io::{self, Write};
5use std::os::unix::io::{AsRawFd, RawFd};
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9struct RedirectOutput {
10 old_stdout: RawFd,
11 old_stderr: RawFd,
12}
13
14impl RedirectOutput {
15 fn new(to_file: &File) -> io::Result<Self> {
16 let stdout = io::stdout();
17 let stderr = io::stderr();
18
19 stdout.lock().flush()?;
20 stderr.lock().flush()?;
21
22 let old_stdout = unsafe { libc::dup(libc::STDOUT_FILENO) };
23 let old_stderr = unsafe { libc::dup(libc::STDERR_FILENO) };
24
25 if old_stdout == -1 || old_stderr == -1 {
26 return Err(io::Error::last_os_error());
27 }
28
29 unsafe {
30 libc::dup2(to_file.as_raw_fd(), libc::STDOUT_FILENO);
31 libc::dup2(to_file.as_raw_fd(), libc::STDERR_FILENO);
32 }
33
34 Ok(RedirectOutput {
35 old_stdout,
36 old_stderr,
37 })
38 }
39}
40
41impl Drop for RedirectOutput {
42 fn drop(&mut self) {
43 let stdout = io::stdout();
44 let stderr = io::stderr();
45
46 stdout.lock().flush().unwrap();
47 stderr.lock().flush().unwrap();
48
49 unsafe {
50 libc::dup2(self.old_stdout, libc::STDOUT_FILENO);
51 libc::dup2(self.old_stderr, libc::STDERR_FILENO);
52 libc::close(self.old_stdout);
53 libc::close(self.old_stderr);
54 }
55 }
56}
57
58struct CopyOutput {
59 old_stdout: RawFd,
60 old_stderr: RawFd,
61 new_fd: Option<RawFd>,
62}
63
64impl CopyOutput {
65 fn new(output_log: &std::path::Path, tee: bool) -> io::Result<Self> {
66 let old_stdout = unsafe { libc::dup(libc::STDOUT_FILENO) };
67 let old_stderr = unsafe { libc::dup(libc::STDERR_FILENO) };
68
69 let new_fd = if tee {
70 let process = Command::new("tee")
71 .arg(output_log)
72 .stdin(std::process::Stdio::piped())
73 .spawn()?;
74 process.stdin.unwrap().as_raw_fd()
75 } else {
76 File::create(output_log)?.as_raw_fd()
77 };
78
79 unsafe {
80 libc::dup2(new_fd, libc::STDOUT_FILENO);
81 libc::dup2(new_fd, libc::STDERR_FILENO);
82 }
83
84 Ok(CopyOutput {
85 old_stdout,
86 old_stderr,
87 new_fd: Some(new_fd),
88 })
89 }
90}
91
92impl Drop for CopyOutput {
93 fn drop(&mut self) {
94 if let Some(fd) = self.new_fd.take() {
95 unsafe {
96 libc::fsync(fd);
97 libc::close(fd);
98 }
99 }
100
101 unsafe {
102 libc::dup2(self.old_stdout, libc::STDOUT_FILENO);
103 libc::dup2(self.old_stderr, libc::STDERR_FILENO);
104 libc::close(self.old_stdout);
105 libc::close(self.old_stderr);
106 }
107 }
108}
109
110pub fn rotate_logfile(source_path: &std::path::Path) -> std::io::Result<()> {
119 if source_path.exists() {
120 let directory_path = source_path.parent().unwrap_or_else(|| Path::new(""));
121 let name = source_path.file_name().unwrap().to_str().unwrap();
122
123 let mut i = 1;
124 while directory_path.join(format!("{}.{}", name, i)).exists() {
125 i += 1;
126 }
127
128 let target_path: PathBuf = directory_path.join(format!("{}.{}", name, i));
129 fs::rename(source_path, &target_path)?;
130
131 debug!("Storing previous build log at {}", target_path.display());
132 }
133 Ok(())
134}
135
136pub enum LogMode {
138 Copy,
140 Redirect,
142}
143
144pub trait LogManager {
146 fn start(&mut self) -> std::io::Result<()>;
152
153 fn stop(&mut self) {}
155}
156
157pub fn wrap<R>(logs: &mut dyn LogManager, f: impl FnOnce() -> R) -> R {
159 logs.start().unwrap();
160 let result = f();
161 std::io::stdout().flush().unwrap();
162 std::io::stderr().flush().unwrap();
163 logs.stop();
164 result
165}
166
167pub struct DirectoryLogManager {
169 path: PathBuf,
170 mode: LogMode,
171 copy_output: Option<CopyOutput>,
172 redirect_output: Option<RedirectOutput>,
173}
174
175impl DirectoryLogManager {
176 pub fn new(path: PathBuf, mode: LogMode) -> Self {
185 Self {
186 path,
187 mode,
188 copy_output: None,
189 redirect_output: None,
190 }
191 }
192}
193
194impl LogManager for DirectoryLogManager {
195 fn start(&mut self) -> std::io::Result<()> {
196 rotate_logfile(&self.path)?;
197 match self.mode {
198 LogMode::Copy => {
199 self.copy_output = Some(CopyOutput::new(&self.path, true)?);
200 }
201 LogMode::Redirect => {
202 self.redirect_output = Some(RedirectOutput::new(&File::create(&self.path)?)?);
203 }
204 }
205 Ok(())
206 }
207
208 fn stop(&mut self) {
209 self.copy_output = None;
210 self.redirect_output = None;
211 }
212}
213
214pub struct NoLogManager;
216
217impl NoLogManager {
218 pub fn new() -> Self {
223 Self {}
224 }
225}
226
227impl Default for NoLogManager {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233impl LogManager for NoLogManager {
234 fn start(&mut self) -> std::io::Result<()> {
235 Ok(())
236 }
237}