Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/macos/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ pub enum Event {
flags: u64,
fd: i32,
},
Rename {
from: String,
to: String,
},
}

/// Converts Darwin `open(2)` flags to the Linux numbers the trace format
Expand Down
54 changes: 53 additions & 1 deletion src/macos/recon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ const GUARDED_OPEN_DPROTECTED: u32 = 0x040c_0790;
const OPENAT: u32 = 0x040c_073c;
const OPENAT_NOCANCEL: u32 = 0x040c_0740;
const OPENAT_DPROTECTED: u32 = 0x040c_0368;
const RENAME: u32 = 0x040c_0200;
const RENAMEAT: u32 = 0x040c_0744;
const RENAMEATX: u32 = 0x040c_07a0;
const CHDIR: u32 = 0x040c_0030;
const FCHDIR: u32 = 0x040c_0034;
const CLOSE: u32 = 0x040c_0018;
Expand Down Expand Up @@ -719,6 +722,28 @@ impl Collector {
return Ok(());
}
match code {
RENAME | RENAMEAT | RENAMEATX => {
if !self.file_events || call.lookups.len() < 2 {
return Ok(());
}
// A rename looks up its source and then its destination.
let at = code != RENAME;
let last = call.lookups.len() - 1;
let (from_raw, to_raw) =
(call.lookups[last - 1].clone(), call.lookups[last].clone());
let from_dir = at.then(|| call.args[0]).filter(|fd| *fd as i32 != AT_FDCWD);
let to_dir = at.then(|| call.args[2]).filter(|fd| *fd as i32 != AT_FDCWD);
let from = self.resolve(pid, &from_raw, from_dir);
let to = self.resolve(pid, &to_raw, to_dir);
if let (Some(from), Some(to)) = (from, to) {
out(Message {
mach_time,
pid,
ppid: 0,
event: Event::Rename { from, to },
})?;
}
}
CHDIR => {
if let Some(raw) = call.lookups.first().cloned() {
let resolved = self.resolve(pid, &raw, None);
Expand Down Expand Up @@ -805,7 +830,10 @@ fn is_followed(code: u32) -> bool {
is_open(code)
|| matches!(
code,
CHDIR
RENAME
| RENAMEAT
| RENAMEATX
| CHDIR
| FCHDIR
| CLOSE
| CLOSE_NOCANCEL
Expand Down Expand Up @@ -1028,6 +1056,29 @@ mod tests {
assert_eq!(paths, ["/src/dir", "/src/dir/b.o", "/src/sub/c.o"]);
}

#[test]
fn reports_a_rename_with_both_paths_resolved() {
let mut stream = Stream::new();
stream
.push(10, RENAME | 1, [0, 0, 0, 0])
.lookup(10, 0x1234, "a-1234.o.tmp")
.lookup(10, 0x5678, "a.o")
.push(10, RENAME | 2, [0, 0, 0, 0]);
let messages = stream.collect(&mut collector());
assert_eq!(
messages,
[Message {
mach_time: 1_040,
pid: 100,
ppid: 0,
event: Event::Rename {
from: "/src/a-1234.o.tmp".into(),
to: "/src/a.o".into(),
},
}]
);
}

#[test]
fn a_new_process_inherits_and_is_reported() {
let mut stream = Stream::new();
Expand Down Expand Up @@ -1297,6 +1348,7 @@ mod tests {
Event::Exec { argv, .. } => format!("{} exec {}", message.pid, argv.join(" ")),
Event::Open { path, .. } => format!("{} open {path}", message.pid),
Event::Exit { .. } => format!("{} exit", message.pid),
Event::Rename { .. } => format!("{} rename", message.pid),
})
.collect();
assert_eq!(
Expand Down
26 changes: 23 additions & 3 deletions src/macos/tracker.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Copyright 2026 The Buildprof Authors.
// SPDX-License-Identifier: Apache-2.0

//! Turns the collector's events into the same process segments and file opens
//! the Linux tracer records.
//! Turns the collector's events into the same process segments, file opens and
//! renames the Linux tracer records.

use super::event::{Event, Message};
use crate::blind_spots::BlindSpots;
use crate::model::{FileOpen, Process, Segment, exit_code};
use crate::model::{FileOpen, Process, Rename, Segment, exit_code};
use crate::perfetto::Writer;
use std::collections::{HashMap, HashSet};
use std::io;
Expand All @@ -22,6 +22,7 @@ pub trait Sink {
fn process_started(&mut self, pid: i32) -> io::Result<()>;
fn segment(&mut self, process: Process, segment: &Segment) -> io::Result<()>;
fn file_open(&mut self, pid: i32, open: &FileOpen) -> io::Result<()>;
fn rename(&mut self, pid: i32, rename: &Rename) -> io::Result<()>;
}

/// The trace being recorded.
Expand All @@ -39,6 +40,9 @@ impl Sink for TraceSink<'_> {
fn file_open(&mut self, pid: i32, open: &FileOpen) -> io::Result<()> {
self.writer.file_open(pid, open)
}
fn rename(&mut self, pid: i32, rename: &Rename) -> io::Result<()> {
self.writer.rename(pid, rename)
}
}

/// Converts `mach_absolute_time` ticks to nanoseconds since recording began.
Expand Down Expand Up @@ -179,6 +183,17 @@ impl<'a, S: Sink> Tracker<'a, S> {
},
)?
}
Event::Rename { from, to } => {
announce(self.sink, &mut self.announced, pid)?;
self.sink.rename(
pid,
&Rename {
timestamp_ns,
from,
to,
},
)?
}
}
Ok(())
}
Expand Down Expand Up @@ -335,6 +350,11 @@ mod tests {
));
Ok(())
}
fn rename(&mut self, pid: i32, rename: &Rename) -> io::Result<()> {
self.0
.push(format!("rename {pid} {} -> {}", rename.from, rename.to));
Ok(())
}
}

fn clock() -> Clock {
Expand Down
Loading