Skip to main content

starnix_modules_tracefs/
fs.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    fn manages_timestamps(&self) -> bool {
52        true
53    }
54}
55
56impl TraceFs {
57    pub fn new_fs(
58        current_task: &CurrentTask,
59        options: FileSystemOptions,
60    ) -> Result<FileSystemHandle, Errno> {
61        let kernel = current_task.kernel();
62        let event_queue_collection = TraceEventQueueList::from(kernel);
63        let fs = FileSystem::new(kernel, CacheMode::Uncached, TraceFs, options)?;
64        let dir = SimpleDirectory::new();
65        dir.edit(&fs, |dir| {
66            dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o755));
67            dir.subdir("per_cpu", 0o755, |dir| {
68                /// A name for each cpu directory, cached to provide a 'static lifetime.
69                static CPU_DIR_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
70                    (0..zx::system_get_num_cpus()).map(|cpu| format!("cpu{}", cpu)).collect()
71                });
72                for (cpu, dir_name) in CPU_DIR_NAMES.iter().map(|s| s.as_str()).enumerate() {
73                    dir.subdir(dir_name, 0o755, |dir| {
74                        let ops: Box<dyn FsNodeOps> = Box::new(TraceRawFile::new_node(
75                            event_queue_collection.queues[cpu].clone(),
76                        ));
77                        dir.entry("trace_pipe_raw", ops, mode!(IFREG, 0o444));
78                        dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o444));
79                    });
80                }
81            });
82            dir.subdir("events", 0o755, |dir| {
83                dir.entry("header_page", EventsHeaderPage::new_node(), mode!(IFREG, 0o444));
84                dir.subdir("ftrace", 0o755, |dir| {
85                    dir.subdir("print", 0o755, |dir| {
86                        dir.entry("format", FtracePrintFormatFile::new_node(), mode!(IFREG, 0o444));
87                    });
88                    if kernel.features.selinux_test_suite {
89                        // Necessary for the perf_event SELinux testsuite case.
90                        // See https://fxbug.dev/398663320.
91                        dir.subdir("function", 0o755, |dir| {
92                            dir.entry(
93                                "id",
94                                BytesFile::new_node(b"1\n".to_vec()),
95                                mode!(IFREG, 0o444),
96                            );
97                        });
98                    }
99                });
100                dir.entry("enable", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
101            });
102            dir.subdir("options", 0o755, |dir| {
103                dir.entry("overwrite", TraceBytesFile::new_node(), mode!(IFREG, 0o444));
104            });
105            dir.entry(
106                "tracing_on",
107                TracingOnFile::new_node(event_queue_collection.clone()),
108                mode!(IFREG, 0o666),
109            );
110            dir.entry("current_tracer", ConstFile::new_node("nop".into()), mode!(IFREG, 0o755));
111            dir.entry(
112                "trace_marker",
113                TraceMarkerFile::new_node(event_queue_collection.clone()),
114                mode!(IFREG, 0o222),
115            );
116            dir.entry("printk_formats", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
117            dir.entry(
118                "trace_clock",
119                ConstFile::new_node(
120                    "[local] global counter uptime perf mono mono_raw boot tai x86-tsc ".into(),
121                ),
122                mode!(IFREG, 0o755),
123            );
124            dir.entry(
125                "buffer_size_kb",
126                BufferSizeKbFile::new_node(event_queue_collection.clone()),
127                mode!(IFREG, 0o666),
128            );
129        });
130
131        let root_ino = fs.allocate_ino();
132        fs.create_root(root_ino, dir);
133        Ok(fs)
134    }
135}
136
137#[derive(Clone)]
138struct BufferSizeKbFile {
139    event_queue_collection: Arc<TraceEventQueueList>,
140}
141
142impl BufferSizeKbFile {
143    pub fn new_node(event_queue_collection: Arc<TraceEventQueueList>) -> impl FsNodeOps {
144        BytesFile::new_node(Self { event_queue_collection })
145    }
146}
147
148impl BytesFileOps for BufferSizeKbFile {
149    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
150        let text = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
151        let trimmed = text.trim();
152        let size_kb: usize = trimmed.parse().map_err(|_| errno!(EINVAL))?;
153        self.event_queue_collection.set_buffer_size_kb(size_kb)?;
154        Ok(())
155    }
156
157    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
158        let size_kb = self.event_queue_collection.buffer_size_kb();
159        let s = format!("{}\n", size_kb);
160        Ok(Cow::Owned(s.into_bytes()))
161    }
162}
163
164#[derive(Default)]
165struct TraceBytesFile {
166    data: LockDepMutex<Vec<u8>, TraceFsDataLock>,
167}
168
169impl TraceBytesFile {
170    #[track_caller]
171    fn new_node() -> impl FsNodeOps {
172        SimpleFileNode::new(move |_| Ok(BytesFile::new(Self::default())))
173    }
174}
175
176impl BytesFileOps for TraceBytesFile {
177    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
178        *self.data.lock() = data;
179        Ok(())
180    }
181    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
182        let data = self.data.lock().clone();
183        Ok(data.into())
184    }
185}
186
187#[derive(Clone)]
188struct TraceRawFile {
189    queue: Arc<TraceEventQueue>,
190}
191
192impl TraceRawFile {
193    pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
194        SimpleFileNode::new(move |_| Ok(Self { queue: queue.clone() }))
195    }
196}
197
198impl FileOps for TraceRawFile {
199    fileops_impl_nonseekable!();
200    fileops_impl_noop_sync!();
201
202    fn read(
203        &self,
204        _file: &FileObject,
205        current_task: &CurrentTask,
206        _offset: usize,
207        data: &mut dyn OutputBuffer,
208    ) -> Result<usize, Errno> {
209        // TODO(b/505532201): Consider supporting larger buffers.
210        if data.available() as u64 != *PAGE_SIZE {
211            return error!(EINVAL);
212        }
213        let _guard = fuchsia_trace::async_enter!(
214            self.queue.async_id_read,
215            CATEGORY_TRACE_META,
216            self.queue.read_track_name(),
217            "tid" => current_task.tid.id
218        );
219        self.queue.read(data)
220    }
221
222    fn write(
223        &self,
224        _file: &FileObject,
225        _current_task: &CurrentTask,
226        _offset: usize,
227        _data: &mut dyn starnix_core::vfs::InputBuffer,
228    ) -> Result<usize, Errno> {
229        error!(ENOSYS)
230    }
231}
232
233#[derive(Clone)]
234struct TracingOnFile {
235    event_queue_collection: Arc<TraceEventQueueList>,
236}
237
238impl TracingOnFile {
239    pub fn new_node(event_queue_collection: Arc<TraceEventQueueList>) -> impl FsNodeOps {
240        BytesFile::new_node(Self { event_queue_collection })
241    }
242}
243
244impl BytesFileOps for TracingOnFile {
245    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
246        let state_str = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
247        let clean_state_str = state_str.split('\n').next().unwrap_or("");
248        match clean_state_str {
249            "0" => self.event_queue_collection.disable(),
250            "1" => self.event_queue_collection.enable(),
251            _ => error!(EINVAL),
252        }
253    }
254
255    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
256        if self.event_queue_collection.is_enabled() {
257            Ok(Cow::Borrowed(&b"1\n"[..]))
258        } else {
259            Ok(Cow::Borrowed(&b"0\n"[..]))
260        }
261    }
262}
263
264#[derive(Clone)]
265struct EventsHeaderPage;
266impl EventsHeaderPage {
267    fn new_node() -> impl FsNodeOps {
268        ConstFile::new_node(
269            format!(
270                "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
271            \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
272            \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
273            \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
274                *PAGE_SIZE - 16
275            )
276            .into_bytes(),
277        )
278    }
279}
280
281#[derive(Clone)]
282struct FtracePrintFormatFile;
283impl FtracePrintFormatFile {
284    fn new_node() -> impl FsNodeOps {
285        ConstFile::new_node(
286            "name: print\n\
287        ID: 5\n\
288        format:\n\
289        \tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n\
290        \tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n\
291        \tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n\
292        \tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n\
293        \n\
294        \tfield:unsigned long ip;\toffset:8;\tsize:8;\tsigned:0;\n\
295        \tfield:char buf[];\toffset:16;\tsize:0;\tsigned:0;\n\
296        \n\
297        print fmt: \"%ps: %s\", (void *)REC->ip, REC- >buf\n"
298                .into(),
299        )
300    }
301}
302
303#[derive(Clone)]
304struct TraceFile;
305impl TraceFile {
306    fn new_node() -> impl FsNodeOps {
307        track_stub!(TODO("https://fxbug.dev/357665908"), "/sys/kernel/tracing/trace");
308        ConstFile::new_node("# tracer: nop\n".into())
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315    use starnix_core::testing::spawn_kernel_and_run;
316    use starnix_core::vfs::buffers::{InputBuffer, VecInputBuffer, VecOutputBuffer};
317    use starnix_core::vfs::{FsNodeInfo, NamespaceNode, OpenAccessCheck};
318    use starnix_uapi::auth::FsCred;
319    use starnix_uapi::file_mode::mode;
320    use starnix_uapi::open_flags::OpenFlags;
321
322    #[::fuchsia::test]
323    async fn test_events_header_page_size() {
324        spawn_kernel_and_run(async move |current_task| {
325            let fs = current_task.fs().root().entry.node.fs();
326            let info = FsNodeInfo::new(mode!(IFREG, 0o444), FsCred::root());
327            let node = fs.create_node_and_allocate_node_id(EventsHeaderPage::new_node(), info);
328            let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
329            let file = ns_node
330                .open(current_task, OpenAccessCheck::skip(OpenFlags::RDONLY))
331                .expect("open header_page node");
332
333            let mut buffer = VecOutputBuffer::new(*PAGE_SIZE as usize);
334            let bytes_read = file.read(current_task, &mut buffer).expect("read header_page");
335            let content = std::str::from_utf8(&buffer.data()[..bytes_read]).expect("valid utf8");
336
337            let expected_content = format!(
338                "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
339            \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
340            \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
341            \tfield: char data;\toffset:16;\tsize:{};\tsigned:0\n",
342                *PAGE_SIZE - 16
343            );
344            assert_eq!(
345                content, expected_content,
346                "Expected header_page to match exactly:\n{}\nGot:\n{}",
347                expected_content, content
348            );
349        })
350        .await;
351    }
352
353    #[::fuchsia::test]
354    async fn test_trace_marker_write_small_and_large_and_disabled() {
355        spawn_kernel_and_run(async move |current_task| {
356            let kernel = current_task.kernel();
357            let event_queue_collection = TraceEventQueueList::from(kernel);
358            let fs = TraceFs::new_fs(current_task, FileSystemOptions::default())
359                .expect("create tracefs");
360            assert!(fs.manages_timestamps());
361            let info = FsNodeInfo::new(mode!(IFREG, 0o222), FsCred::root());
362            let node = fs.create_node_and_allocate_node_id(
363                TraceMarkerFile::new_node(event_queue_collection.clone()),
364                info,
365            );
366            let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
367            let file = ns_node
368                .open(current_task, OpenAccessCheck::skip(OpenFlags::WRONLY))
369                .expect("open trace_marker node");
370            let mtime_before = file.node().info().time_modify;
371
372            // 1. When disabled, write should drain the input buffer and return the full byte count.
373            event_queue_collection.disable().expect("disable queue");
374            let mut disabled_buf = VecInputBuffer::new(b"B|1234|disabled_slice\n");
375            let written = file.write(current_task, &mut disabled_buf).expect("write disabled");
376            assert_eq!(written, b"B|1234|disabled_slice\n".len());
377            assert_eq!(disabled_buf.available(), 0);
378            assert_eq!(file.node().info().time_modify, mtime_before);
379
380            // 2. When enabled, small writes (<= 128 bytes) use the inline SmallVec buffer,
381            // and large writes (> 128 bytes) spill to the heap. Write enough events across
382            // the per-CPU queues to overflow a full page on queue 0 so we can read it back.
383            event_queue_collection.enable().expect("enable queue");
384            let num_cpus = event_queue_collection.queues.len();
385            for _ in 0..num_cpus {
386                let mut small_buf = VecInputBuffer::new(b"B|1234|activityStart\n");
387                let written = file.write(current_task, &mut small_buf).expect("write small");
388                assert_eq!(written, b"B|1234|activityStart\n".len());
389                assert_eq!(small_buf.available(), 0);
390            }
391            let large_payload = vec![b'x'; 768];
392            for _ in 0..(6 * num_cpus) {
393                let mut large_buf = VecInputBuffer::new(&large_payload);
394                let written = file.write(current_task, &mut large_buf).expect("write large");
395                assert_eq!(written, large_payload.len());
396                assert_eq!(large_buf.available(), 0);
397            }
398            assert_eq!(file.node().info().time_modify, mtime_before);
399
400            let mut read_buf = VecOutputBuffer::new(*PAGE_SIZE as usize);
401            let bytes_read = event_queue_collection.queues[0]
402                .read(&mut read_buf)
403                .expect("read full page from queue 0");
404            assert_eq!(bytes_read, *PAGE_SIZE as usize);
405            let page_data = read_buf.data();
406            assert!(
407                page_data
408                    .windows(b"B|1234|activityStart\n".len())
409                    .any(|w| w == b"B|1234|activityStart\n")
410            );
411            assert!(page_data.windows(large_payload.len()).any(|w| w == large_payload.as_slice()));
412            event_queue_collection.disable().expect("disable queue after test");
413        })
414        .await;
415    }
416
417    #[::fuchsia::test]
418    async fn test_buffer_size_kb_read_write() {
419        spawn_kernel_and_run(async move |current_task| {
420            let kernel = current_task.kernel();
421            let event_queue_collection = TraceEventQueueList::from(kernel);
422            let fs = current_task.fs().root().entry.node.fs();
423            let info = FsNodeInfo::new(mode!(IFREG, 0o666), FsCred::root());
424            let node = fs.create_node_and_allocate_node_id(
425                BufferSizeKbFile::new_node(event_queue_collection.clone()),
426                info,
427            );
428            let ns_node = NamespaceNode::new_anonymous_unrooted(current_task, node);
429            let file = ns_node
430                .open(current_task, OpenAccessCheck::skip(OpenFlags::RDWR))
431                .expect("open buffer_size_kb node");
432
433            let mut buffer = VecOutputBuffer::new(64);
434            let bytes_read =
435                file.read_at(current_task, 0, &mut buffer).expect("read buffer_size_kb");
436            assert_eq!(&buffer.data()[..bytes_read], b"2048\n");
437
438            let mut input = starnix_core::vfs::buffers::VecInputBuffer::new(b"4096\n");
439            let bytes_written = file.write(current_task, &mut input).expect("write buffer_size_kb");
440            assert_eq!(bytes_written, 5);
441
442            let mut buffer2 = VecOutputBuffer::new(64);
443            let bytes_read2 =
444                file.read_at(current_task, 0, &mut buffer2).expect("read updated buffer_size_kb");
445            assert_eq!(&buffer2.data()[..bytes_read2], b"4096\n");
446
447            // Writing smaller than 3 pages clamps to 12 KB
448            let mut input3 = starnix_core::vfs::buffers::VecInputBuffer::new(b"4\n");
449            file.write(current_task, &mut input3).expect("write small buffer_size_kb");
450            let mut buffer3 = VecOutputBuffer::new(64);
451            let bytes_read3 =
452                file.read_at(current_task, 0, &mut buffer3).expect("read clamped buffer_size_kb");
453            assert_eq!(&buffer3.data()[..bytes_read3], b"12\n");
454
455            // Writing 0 returns EINVAL
456            let mut input_zero = starnix_core::vfs::buffers::VecInputBuffer::new(b"0\n");
457            assert_eq!(file.write(current_task, &mut input_zero), error!(EINVAL));
458
459            // Writing non-numeric returns EINVAL
460            let mut input_bad = starnix_core::vfs::buffers::VecInputBuffer::new(b"invalid\n");
461            assert_eq!(file.write(current_task, &mut input_bad), error!(EINVAL));
462
463            // Writing while enabled also succeeds
464            event_queue_collection.enable().expect("enable tracing");
465            let mut input_enabled = starnix_core::vfs::buffers::VecInputBuffer::new(b"2048\n");
466            let bytes_written_enabled =
467                file.write(current_task, &mut input_enabled).expect("write enabled buffer_size_kb");
468            assert_eq!(bytes_written_enabled, 5);
469            let mut buffer_enabled = VecOutputBuffer::new(64);
470            let bytes_read_enabled = file
471                .read_at(current_task, 0, &mut buffer_enabled)
472                .expect("read buffer_size_kb while enabled");
473            assert_eq!(&buffer_enabled.data()[..bytes_read_enabled], b"2048\n");
474            event_queue_collection.disable().expect("disable tracing");
475        })
476        .await;
477    }
478}