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;
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::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked};
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    locked: &mut Locked<Unlocked>,
28    current_task: &CurrentTask,
29    options: FileSystemOptions,
30) -> Result<FileSystemHandle, Errno> {
31    struct TraceFsHandle(FileSystemHandle);
32
33    let handle = current_task.kernel().expando.get_or_init(|| {
34        TraceFsHandle(
35            TraceFs::new_fs(locked, current_task, options)
36                .expect("tracefs constructed with valid options"),
37        )
38    });
39    Ok(handle.0.clone())
40}
41
42pub struct TraceFs;
43
44impl FileSystemOps for TraceFs {
45    fn statfs(
46        &self,
47        _locked: &mut Locked<FileOpsCore>,
48        _fs: &FileSystem,
49        _current_task: &CurrentTask,
50    ) -> Result<statfs, Errno> {
51        Ok(default_statfs(TRACEFS_MAGIC))
52    }
53
54    fn name(&self) -> &'static FsStr {
55        "tracefs".into()
56    }
57}
58
59impl TraceFs {
60    pub fn new_fs<L>(
61        locked: &mut Locked<L>,
62        current_task: &CurrentTask,
63        options: FileSystemOptions,
64    ) -> Result<FileSystemHandle, Errno>
65    where
66        L: LockEqualOrBefore<FileOpsCore>,
67    {
68        let kernel = current_task.kernel();
69        // Only use a single queue until b/357665908 is addressed.
70        let trace_event_queue = TraceEventQueue::from(kernel);
71        let fs = FileSystem::new(locked, kernel, CacheMode::Uncached, TraceFs, options)?;
72        let dir = SimpleDirectory::new();
73        dir.edit(&fs, |dir| {
74            dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o755));
75            dir.subdir("per_cpu", 0o755, |dir| {
76                /// A name for each cpu directory, cached to provide a 'static lifetime.
77                static CPU_DIR_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
78                    (0..zx::system_get_num_cpus()).map(|cpu| format!("cpu{}", cpu)).collect()
79                });
80                for dir_name in CPU_DIR_NAMES.iter().map(|s| s.as_str()) {
81                    dir.subdir(dir_name, 0o755, |dir| {
82                        // We're not able to detect which cpu events are coming from, so we push them
83                        // all into the first cpu.
84                        let ops: Box<dyn FsNodeOps> = if dir_name == "cpu0" {
85                            Box::new(TraceRawFile::new_node(trace_event_queue.clone()))
86                        } else {
87                            track_stub!(
88                                TODO("https://fxbug.dev/357665908"),
89                                "/sys/kernel/tracing/per_cpu/cpuX/trace_pipe_raw"
90                            );
91                            Box::new(EmptyFile::new_node())
92                        };
93                        dir.entry("trace_pipe_raw", ops, mode!(IFREG, 0o444));
94                        dir.entry("trace", TraceFile::new_node(), mode!(IFREG, 0o444));
95                    });
96                }
97            });
98            dir.subdir("events", 0o755, |dir| {
99                dir.entry("header_page", EventsHeaderPage::new_node(), mode!(IFREG, 0o444));
100                dir.subdir("ftrace", 0o755, |dir| {
101                    dir.subdir("print", 0o755, |dir| {
102                        dir.entry("format", FtracePrintFormatFile::new_node(), mode!(IFREG, 0o444));
103                    });
104                    if kernel.features.selinux_test_suite {
105                        // Necessary for the perf_event SELinux testsuite case.
106                        // See https://fxbug.dev/398663320.
107                        dir.subdir("function", 0o755, |dir| {
108                            dir.entry(
109                                "id",
110                                BytesFile::new_node(b"1\n".to_vec()),
111                                mode!(IFREG, 0o444),
112                            );
113                        });
114                    }
115                });
116                dir.entry("enable", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
117            });
118            dir.subdir("options", 0o755, |dir| {
119                dir.entry("overwrite", TraceBytesFile::new_node(), mode!(IFREG, 0o444));
120            });
121            dir.entry(
122                "tracing_on",
123                TracingOnFile::new_node(trace_event_queue.clone()),
124                mode!(IFREG, 0o666),
125            );
126            dir.entry("current_tracer", ConstFile::new_node("nop".into()), mode!(IFREG, 0o755));
127            dir.entry(
128                "trace_marker",
129                TraceMarkerFile::new_node(trace_event_queue.clone()),
130                mode!(IFREG, 0o222),
131            );
132            dir.entry("printk_formats", TraceBytesFile::new_node(), mode!(IFREG, 0o755));
133            dir.entry(
134                "trace_clock",
135                ConstFile::new_node(
136                    "[local] global counter uptime perf mono mono_raw boot tai x86-tsc ".into(),
137                ),
138                mode!(IFREG, 0o755),
139            );
140            dir.entry("buffer_size_kb", ConstFile::new_node("7".into()), mode!(IFREG, 0o755));
141        });
142
143        let root_ino = fs.allocate_ino();
144        fs.create_root(root_ino, dir);
145        Ok(fs)
146    }
147}
148
149#[derive(Default)]
150struct TraceBytesFile {
151    data: Mutex<Vec<u8>>,
152}
153
154impl TraceBytesFile {
155    #[track_caller]
156    fn new_node() -> impl FsNodeOps {
157        SimpleFileNode::new(move |_, _| Ok(BytesFile::new(Self::default())))
158    }
159}
160
161impl BytesFileOps for TraceBytesFile {
162    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
163        *self.data.lock() = data;
164        Ok(())
165    }
166    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
167        let data = self.data.lock().clone();
168        Ok(data.into())
169    }
170}
171
172#[derive(Clone)]
173struct TraceRawFile {
174    queue: Arc<TraceEventQueue>,
175}
176
177impl TraceRawFile {
178    pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
179        SimpleFileNode::new(move |_, _| Ok(Self { queue: queue.clone() }))
180    }
181}
182
183impl FileOps for TraceRawFile {
184    fileops_impl_nonseekable!();
185    fileops_impl_noop_sync!();
186
187    fn read(
188        &self,
189        _locked: &mut Locked<FileOpsCore>,
190        _file: &FileObject,
191        _current_task: &CurrentTask,
192        _offset: usize,
193        data: &mut dyn OutputBuffer,
194    ) -> Result<usize, Errno> {
195        assert!(data.available() as u64 == *PAGE_SIZE);
196        let _guard = fuchsia_trace::async_enter!(
197            self.queue.async_id_read,
198            CATEGORY_TRACE_META,
199            self.queue.read_track_name()
200        );
201        self.queue.read(data)
202    }
203
204    fn write(
205        &self,
206        _locked: &mut Locked<FileOpsCore>,
207        _file: &FileObject,
208        _current_task: &CurrentTask,
209        _offset: usize,
210        _data: &mut dyn starnix_core::vfs::InputBuffer,
211    ) -> Result<usize, Errno> {
212        error!(ENOSYS)
213    }
214}
215
216#[derive(Default)]
217struct EmptyFile {}
218
219impl EmptyFile {
220    #[track_caller]
221    fn new_node() -> impl FsNodeOps {
222        SimpleFileNode::new(move |_, _| Ok(BytesFile::new(Self::default())))
223    }
224}
225
226impl BytesFileOps for EmptyFile {
227    fn write(&self, _current_task: &CurrentTask, _data: Vec<u8>) -> Result<(), Errno> {
228        error!(ENOTSUP)
229    }
230    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
231        error!(EAGAIN)
232    }
233}
234
235#[derive(Clone)]
236struct TracingOnFile {
237    queue: Arc<TraceEventQueue>,
238}
239
240impl TracingOnFile {
241    pub fn new_node(queue: Arc<TraceEventQueue>) -> impl FsNodeOps {
242        BytesFile::new_node(Self { queue })
243    }
244}
245
246impl BytesFileOps for TracingOnFile {
247    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
248        let state_str = std::str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
249        let clean_state_str = state_str.split('\n').next().unwrap_or("");
250        match clean_state_str {
251            "0" => self.queue.disable(),
252            "1" => self.queue.enable(),
253            _ => error!(EINVAL),
254        }
255    }
256
257    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
258        if self.queue.is_enabled() {
259            Ok(Cow::Borrowed(&b"1\n"[..]))
260        } else {
261            Ok(Cow::Borrowed(&b"0\n"[..]))
262        }
263    }
264}
265
266#[derive(Clone)]
267struct EventsHeaderPage;
268impl EventsHeaderPage {
269    fn new_node() -> impl FsNodeOps {
270        ConstFile::new_node(
271            "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0\n\
272        \tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1\n\
273        \tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1\n\
274        \tfield: char data;\toffset:16;\tsize:4080;\tsigned:0\n"
275                .into(),
276        )
277    }
278}
279
280#[derive(Clone)]
281struct FtracePrintFormatFile;
282impl FtracePrintFormatFile {
283    fn new_node() -> impl FsNodeOps {
284        ConstFile::new_node(
285            "name: print\n\
286        ID: 5\n\
287        format:\n\
288        \tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n\
289        \tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n\
290        \tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n\
291        \tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n\
292        \n\
293        \tfield:unsigned long ip;\toffset:8;\tsize:8;\tsigned:0;\n\
294        \tfield:char buf[];\toffset:16;\tsize:0;\tsigned:0;\n\
295        \n\
296        print fmt: \"%ps: %s\", (void *)REC->ip, REC- >buf\n"
297                .into(),
298        )
299    }
300}
301
302#[derive(Clone)]
303struct TraceFile;
304impl TraceFile {
305    fn new_node() -> impl FsNodeOps {
306        track_stub!(TODO("https://fxbug.dev/357665908"), "/sys/kernel/tracing/trace");
307        ConstFile::new_node("# tracer: nop\n".into())
308    }
309}