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
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                /// A name for each cpu directory, cached to provide a 'static lifetime.
65                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                        // Necessary for the perf_event SELinux testsuite case.
86                        // See https://fxbug.dev/398663320.
87                        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        // TODO(b/505532201): Consider supporting larger buffers.
175        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}