Skip to main content

ognibuild/
logs.rs

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
110/// Rotate a log file, moving it to a new file with a timestamp.
111///
112/// # Arguments
113/// * `source_path` - Path to the log file to rotate
114///
115/// # Returns
116/// * `Ok(())` - If the log file was rotated successfully
117/// * `Err(Error)` - If rotating the log file failed
118pub 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
136/// Mode for logging.
137pub enum LogMode {
138    /// Copy output to the log file.
139    Copy,
140    /// Redirect output to the log file.
141    Redirect,
142}
143
144/// Trait for managing log files for build operations.
145pub trait LogManager {
146    /// Start logging to the log file.
147    ///
148    /// # Returns
149    /// * `Ok(())` - If logging was started successfully
150    /// * `Err(Error)` - If starting logging failed
151    fn start(&mut self) -> std::io::Result<()>;
152
153    /// Stop logging to the log file.
154    fn stop(&mut self) {}
155}
156
157/// Run a function capturing its output to a log file.
158pub 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
167/// Log manager that logs to a file in a directory.
168pub struct DirectoryLogManager {
169    path: PathBuf,
170    mode: LogMode,
171    copy_output: Option<CopyOutput>,
172    redirect_output: Option<RedirectOutput>,
173}
174
175impl DirectoryLogManager {
176    /// Create a new DirectoryLogManager.
177    ///
178    /// # Arguments
179    /// * `path` - Path to the log file
180    /// * `mode` - Mode for logging
181    ///
182    /// # Returns
183    /// A new DirectoryLogManager instance
184    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
214/// Log manager that does nothing.
215pub struct NoLogManager;
216
217impl NoLogManager {
218    /// Create a new NoLogManager.
219    ///
220    /// # Returns
221    /// A new NoLogManager instance
222    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}