Skip to main content

sctrace/
trace.rs

1// SPDX-License-Identifier: MPL-2.0
2
3use std::{
4    error::Error,
5    fmt,
6    fs::File,
7    io::{BufRead, BufReader, Lines, Read},
8    os::unix::io::FromRawFd,
9    path::Path,
10    process::Command,
11};
12
13#[derive(Debug)]
14pub struct TraceError {
15    message: String,
16}
17
18impl fmt::Display for TraceError {
19    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        write!(f, "{}", self.message)
21    }
22}
23
24impl From<TraceError> for String {
25    fn from(err: TraceError) -> Self {
26        err.message
27    }
28}
29
30impl Error for TraceError {}
31
32impl TraceError {
33    fn new(message: &str) -> Self {
34        Self {
35            message: message.to_string(),
36        }
37    }
38}
39
40/// A stream of strace log entries.
41pub struct StraceLogStream(Box<dyn BufRead>);
42
43impl Read for StraceLogStream {
44    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
45        self.0.read(buf)
46    }
47}
48
49impl BufRead for StraceLogStream {
50    fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
51        self.0.fill_buf()
52    }
53
54    fn consume(&mut self, amt: usize) {
55        self.0.consume(amt)
56    }
57}
58
59impl StraceLogStream {
60    /// Creates a new stream by opening an existing strace log file.
61    pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self, TraceError> {
62        let file = File::open(path.as_ref())
63            .map_err(|e| TraceError::new(&format!("Failed to open log file: {}", e)))?;
64        Ok(Self(Box::new(BufReader::new(file))))
65    }
66
67    /// Creates a new stream by running a new command with strace.
68    pub fn run_cmd<P: AsRef<Path>>(path: P, args: Vec<&str>) -> Result<Self, TraceError> {
69        let mut command_str = path.as_ref().to_string_lossy().to_string();
70        for arg in args {
71            command_str.push(' ');
72            command_str.push_str(arg);
73        }
74
75        // Create pipe
76        let (read_fd, write_fd) = nix::unistd::pipe()
77            .map_err(|e| TraceError::new(&format!("Failed to create pipe: {}", e)))?;
78
79        // Convert read end to File
80        let read_file = unsafe { std::fs::File::from_raw_fd(read_fd) };
81
82        // Start strace, using /proc/self/fd/N to access the write end
83        Command::new("strace")
84            .args([
85                "-o",
86                &format!("/proc/self/fd/{}", write_fd),
87                "-yy",
88                "-f",
89                "sh",
90                "-c",
91                &command_str,
92            ])
93            .spawn()
94            .map_err(|e| {
95                // Clean up file descriptors
96                nix::unistd::close(read_fd).ok();
97                nix::unistd::close(write_fd).ok();
98
99                if e.kind() == std::io::ErrorKind::NotFound {
100                    TraceError::new(
101                        "strace command not found. Please install strace:\n\
102                        - Debian/Ubuntu: sudo apt-get install strace\n\
103                        - Fedora/RHEL: sudo dnf install strace",
104                    )
105                } else {
106                    TraceError::new(&format!(
107                        "Failed to start strace: {}\n\
108                        If this is a permission error, try:\n\
109                        sudo sctrace <pattern_file> -- {}",
110                        e, command_str
111                    ))
112                }
113            })?;
114
115        // Close write end (strace has already inherited it)
116        nix::unistd::close(write_fd).ok();
117
118        Ok(Self(Box::new(BufReader::new(read_file))))
119    }
120
121    /// Creates a new stream by a string of strace log.
122    pub fn from_string(log_str: &str) -> Result<Self, TraceError> {
123        let cursor = std::io::Cursor::new(log_str.to_string().into_bytes());
124        Ok(Self(Box::new(BufReader::new(cursor))))
125    }
126
127    /// Returns an iterator over the lines of this stream.
128    pub fn lines(self) -> Lines<Self> {
129        BufRead::lines(self)
130    }
131}