1use super::tracing_directory::TraceMarkerFile;
6use starnix_core::perf::{TraceEventQueue, TraceEventQueueList};
7use starnix_core::task::CurrentTask;
8use starnix_core::vfs::pseudo::dynamic_file::ConstFile;
9use starnix_core::vfs::pseudo::simple_directory::SimpleDirectory;
10use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps, SimpleFileNode};
11use starnix_core::vfs::{
12 CacheMode, FileObject, FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions,
13 FsNodeOps, FsStr, OutputBuffer,
14};
15use starnix_core::{fileops_impl_nonseekable, fileops_impl_noop_sync};
16use starnix_logging::{CATEGORY_TRACE_META, track_stub};
17use starnix_sync::{LockDepMutex, TraceFsDataLock};
18use starnix_types::PAGE_SIZE;
19use starnix_types::vfs::default_statfs;
20use starnix_uapi::errors::Errno;
21use starnix_uapi::file_mode::mode;
22use starnix_uapi::{TRACEFS_MAGIC, errno, error, statfs};
23use std::borrow::Cow;
24use std::sync::{Arc, LazyLock};
25
26pub fn trace_fs(
27 current_task: &CurrentTask,
28 options: FileSystemOptions,
29) -> Result<FileSystemHandle, Errno> {
30 struct TraceFsHandle(FileSystemHandle);
31
32 let handle = current_task.kernel().expando.get_or_init(|| {
33 TraceFsHandle(
34 TraceFs::new_fs(current_task, options).expect("tracefs constructed with valid options"),
35 )
36 });
37 Ok(handle.0.clone())
38}
39
40pub struct TraceFs;
41
42impl FileSystemOps for TraceFs {
43 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
44 Ok(default_statfs(TRACEFS_MAGIC))
45 }
46
47 fn name(&self) -> &'static FsStr {
48 "tracefs".into()
49 }
50}
51
52impl TraceFs {
53 pub fn new_fs(
54 current_task: &CurrentTask,
55 options: FileSystemOptions,
56 ) -> Result<FileSystemHandle, Errno> {
57 let kernel = current_task.kernel();
58 let event_queue_collection = TraceEventQueueList::from(kernel);
59 let fs = FileSystem::new(kernel, CacheMode::Uncached, TraceFs, options)?;
60 let dir = SimpleDirectory::new();
61 dir.edit(&fs, |dir| {
62 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o755));
63 dir.subdir("per_cpu", 0o755, |dir| {
64 static CPU_DIR_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
66 (0..zx::system_get_num_cpus()).map(|cpu| format!("cpu{}", cpu)).collect()
67 });
68 for (cpu, dir_name) in CPU_DIR_NAMES.iter().map(|s| s.as_str()).enumerate() {
69 dir.subdir(dir_name, 0o755, |dir| {
70 let ops: Box<dyn FsNodeOps> = Box::new(TraceRawFile::new_node(
71 event_queue_collection.queues[cpu].clone(),
72 ));
73 dir.entry("trace_pipe_raw", ops, mode!(IFREG, 0o444));
74 dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o444));
75 });
76 }
77 });
78 dir.subdir("events", 0o755, |dir| {
79 dir.entry("header_page", EventsHeaderPage::new_node(), mode!(IFREG, 0o444));
80 dir.subdir("ftrace", 0o755, |dir| {
81 dir.subdir("print", 0o755, |dir| {
82 dir.entry("format", FtracePrintFormatFile::new_node(), mode!(IFREG, 0o444));
83 });
84 if kernel.features.selinux_test_suite {
85 dir.subdir("function", 0o755, |dir| {
88 dir.entry(
89 "id",
90 BytesFile::new_node(b"1\n".to_vec()),
91 mode!(IFREG, 0o444),
92 );
93 });
94 }
95 });
96 dir.entry("enable", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
97 });
98 dir.subdir("options", 0o755, |dir| {
99 dir.entry("overwrite", TraceBytesFile::new_node(), mode!(IFREG, 0o444));
100 });
101 dir.entry(
102 "tracing_on",
103 TracingOnFile::new_node(event_queue_collection.clone()),
104 mode!(IFREG, 0o666),
105 );
106 dir.entry("current_tracer", ConstFile::new_node("nop".into()), mode!(IFREG, 0o755));
107 dir.entry(
108 "trace_marker",
109 TraceMarkerFile::new_node(event_queue_collection.clone()),
110 mode!(IFREG, 0o222),
111 );
112 dir.entry("printk_formats", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
113 dir.entry(
114 "trace_clock",
115 ConstFile::new_node(
116 "[local] global counter uptime perf mono mono_raw boot tai x86-tsc ".into(),
117 ),
118 mode!(IFREG, 0o755),
119 );
120 dir.entry("buffer_size_kb", ConstFile::new_node("7".into()), mode!(IFREG, 0o755));
121 });
122
123 let root_ino = fs.allocate_ino();
124 fs.create_root(root_ino, dir);
125 Ok(fs)
126 }
127}
128
129#[derive(Default)]
130struct TraceBytesFile {
131 data: LockDepMutex<Vec<u8>, TraceFsDataLock>,
132}
133
134impl TraceBytesFile {
135 #[track_caller]
136 fn new_node() -> impl FsNodeOps {
137 SimpleFileNode::new(move |_| Ok(BytesFile::new(Self::default())))
138 }
139}
140
141impl BytesFileOps for TraceBytesFile {
142 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
143 *self.data.lock() = data;
144 Ok(())
145 }
146 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
147 let data = self.data.lock().clone();
148 Ok(data.into())
149 }
150}
151
152#[derive(Clone)]
153struct TraceRawFile {
154 queue: Arc<TraceEventQueue>,
155}
156
157impl TraceRawFile {
158 pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
159 SimpleFileNode::new(move |_| Ok(Self { queue: queue.clone() }))
160 }
161}
162
163impl FileOps for TraceRawFile {
164 fileops_impl_nonseekable!();
165 fileops_impl_noop_sync!();
166
167 fn read(
168 &self,
169 _file: &FileObject,
170 current_task: &CurrentTask,
171 _offset: usize,
172 data: &mut dyn OutputBuffer,
173 ) -> Result<usize, Errno> {
174 if data.available() as u64 != *PAGE_SIZE {
176 return error!(EINVAL);
177 }
178 let _guard = fuchsia_trace::async_enter!(
179 self.queue.async_id_read,
180 CATEGORY_TRACE_META,
181 self.queue.read_track_name(),
182 "tid" => current_task.tid
183 );
184 self.queue.read(data)
185 }
186
187 fn write(
188 &self,
189 _file: &FileObject,
190 _current_task: &CurrentTask,
191 _offset: usize,
192 _data: &mut dyn starnix_core::vfs::InputBuffer,
193 ) -> Result<usize, Errno> {
194 error!(ENOSYS)
195 }
196}
197
198#[derive(Clone)]
199struct TracingOnFile {
200 event_queue_collection: Arc<TraceEventQueueList>,
201}
202
203impl TracingOnFile {
204 pub fn new_node(event_queue_collection: Arc<TraceEventQueueList>) -> impl FsNodeOps {
205 BytesFile::new_node(Self { event_queue_collection })
206 }
207}
208
209impl BytesFileOps for TracingOnFile {
210 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
211 let state_str = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
212 let clean_state_str = state_str.split('\n').next().unwrap_or("");
213 match clean_state_str {
214 "0" => self.event_queue_collection.disable(),
215 "1" => self.event_queue_collection.enable(),
216 _ => error!(EINVAL),
217 }
218 }
219
220 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
221 if self.event_queue_collection.is_enabled() {
222 Ok(Cow::Borrowed(&b"1\n"[..]))
223 } else {
224 Ok(Cow::Borrowed(&b"0\n"[..]))
225 }
226 }
227}
228
229#[derive(Clone)]
230struct EventsHeaderPage;
231impl EventsHeaderPage {
232 fn new_node() -> impl FsNodeOps {
233 ConstFile::new_node(
234 format!(
235 "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
236 \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
237 \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
238 \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
239 *PAGE_SIZE - 16
240 )
241 .into_bytes(),
242 )
243 }
244}
245
246#[derive(Clone)]
247struct FtracePrintFormatFile;
248impl FtracePrintFormatFile {
249 fn new_node() -> impl FsNodeOps {
250 ConstFile::new_node(
251 "name: print\n\
252 ID: 5\n\
253 format:\n\
254 \tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n\
255 \tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n\
256 \tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n\
257 \tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n\
258 \n\
259 \tfield:unsigned long ip;\toffset:8;\tsize:8;\tsigned:0;\n\
260 \tfield:char buf[];\toffset:16;\tsize:0;\tsigned:0;\n\
261 \n\
262 print fmt: \"%ps: %s\", (void *)REC->ip, REC- >buf\n"
263 .into(),
264 )
265 }
266}
267
268#[derive(Clone)]
269struct TraceFile;
270impl TraceFile {
271 fn new_node() -> impl FsNodeOps {
272 track_stub!(TODO("https://fxbug.dev/357665908"), "/sys/kernel/tracing/trace");
273 ConstFile::new_node("# tracer: nop\n".into())
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use starnix_core::testing::spawn_kernel_and_run;
281 use starnix_core::vfs::buffers::VecOutputBuffer;
282 use starnix_core::vfs::{FsNodeInfo, NamespaceNode};
283 use starnix_uapi::auth::FsCred;
284 use starnix_uapi::file_mode::{AccessCheck, mode};
285 use starnix_uapi::open_flags::OpenFlags;
286
287 #[::fuchsia::test]
288 async fn test_events_header_page_size() {
289 spawn_kernel_and_run(async move |current_task| {
290 let fs = current_task.fs().root().entry.node.fs();
291 let info = FsNodeInfo::new(mode!(IFREG, 0o444), FsCred::root());
292 let node = fs.create_node_and_allocate_node_id(EventsHeaderPage::new_node(), info);
293 let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
294 let file = ns_node
295 .open(current_task, OpenFlags::RDONLY, AccessCheck::skip())
296 .expect("open header_page node");
297
298 let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
299 let bytes_read = file.read(current_task, &mut buffer).expect("read header_page");
300 let content = std::str::from_utf8(&buffer.data()[..bytes_read]).expect("valid utf8");
301
302 let expected_content = format!(
303 "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
304 \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
305 \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
306 \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
307 *PAGE_SIZE - 16
308 );
309 assert_eq!(
310 content, expected_content,
311 "Expected header_page to match exactly:\n{}\nGot:\n{}",
312 expected_content, content
313 );
314 })
315 .await;
316 }
317}