Skip to main content

starnix_core/device/
mem.rs

1// Copyright 2021 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 crate::device::kobject::DeviceMetadata;
6use crate::device::{DeviceMode, simple_device_ops};
7use crate::mm::{
8    DesiredAddress, MappingName, MappingOptions, MemoryAccessorExt, ProtectionFlags,
9    create_anonymous_mapping_memory,
10};
11use crate::task::syslog::{self, KmsgLevel};
12use crate::task::{
13    CurrentTask, EventHandler, Kernel, LogSubscription, Syslog, SyslogAccess, WaitCanceler, Waiter,
14};
15use crate::vfs::buffers::{InputBuffer, InputBufferExt as _, OutputBuffer};
16use crate::vfs::{
17    Anon, FileHandle, FileObject, FileOps, NamespaceNode, SeekTarget, fileops_impl_noop_sync,
18    fileops_impl_seekless,
19};
20use starnix_logging::{Level, track_stub};
21use starnix_sync::{DevKmsgLock, LockDepMutex};
22use starnix_uapi::device_id::DeviceId;
23use starnix_uapi::error;
24use starnix_uapi::errors::Errno;
25use starnix_uapi::open_flags::OpenFlags;
26use starnix_uapi::user_address::UserAddress;
27use starnix_uapi::vfs::FdEvents;
28use std::mem::MaybeUninit;
29
30#[derive(Default)]
31pub struct DevNull;
32
33pub fn new_null_file(current_task: &CurrentTask, flags: OpenFlags) -> FileHandle {
34    Anon::new_private_file(current_task, Box::new(DevNull), flags, "[fuchsia:null]")
35}
36
37impl FileOps for DevNull {
38    fileops_impl_seekless!();
39    fileops_impl_noop_sync!();
40
41    fn write(
42        &self,
43        _file: &FileObject,
44        _current_task: &CurrentTask,
45        _offset: usize,
46        data: &mut dyn InputBuffer,
47    ) -> Result<usize, Errno> {
48        // TODO(https://fxbug.dev/453758455) align /dev/null behavior with Linux
49        // Writes to /dev/null on Linux treat the input buffer in an unconventional way. The actual
50        // data is not touched and if the input parameters are plausible the device claims to
51        // successfully write up to MAX_RW_COUNT bytes.  If the input parameters are outside of the
52        // user accessible address space, writes will return EFAULT.
53        let bytes_logged = match data.read_to_vec_limited(data.available()) {
54            Ok(bytes) => bytes.len(),
55            Err(_) => 0,
56        };
57
58        Ok(bytes_logged + data.drain())
59    }
60
61    fn read(
62        &self,
63        _file: &FileObject,
64        _current_task: &CurrentTask,
65        _offset: usize,
66        _data: &mut dyn OutputBuffer,
67    ) -> Result<usize, Errno> {
68        Ok(0)
69    }
70
71    fn to_handle(
72        &self,
73        _file: &FileObject,
74        _current_task: &CurrentTask,
75    ) -> Result<Option<zx::NullableHandle>, Errno> {
76        Ok(None)
77    }
78}
79
80#[derive(Default)]
81struct DevZero;
82impl FileOps for DevZero {
83    fileops_impl_seekless!();
84    fileops_impl_noop_sync!();
85
86    fn mmap(
87        &self,
88        file: &FileObject,
89        current_task: &CurrentTask,
90        addr: DesiredAddress,
91        memory_offset: u64,
92        length: usize,
93        prot_flags: ProtectionFlags,
94        mut options: MappingOptions,
95        filename: NamespaceNode,
96    ) -> Result<UserAddress, Errno> {
97        // All /dev/zero mappings behave as anonymous mappings.
98        //
99        // This means that we always create a new zero-filled VMO for this mmap request.
100        // Memory is never shared between two mappings of /dev/zero, even if
101        // `MappingOptions::SHARED` is set.
102        //
103        // Similar to anonymous mappings, if this process were to request a shared mapping
104        // of /dev/zero and then fork, the child and the parent process would share the
105        // VMO created here.
106        let memory = create_anonymous_mapping_memory(length as u64)?;
107
108        options |= MappingOptions::ANONYMOUS;
109
110        current_task.mm()?.map_memory(
111            addr,
112            memory,
113            memory_offset,
114            length,
115            prot_flags,
116            file.max_access_for_memory_mapping(),
117            options,
118            // We set the filename here, even though we are creating what is
119            // functionally equivalent to an anonymous mapping. Doing so affects
120            // the output of `/proc/self/maps` and identifies this mapping as
121            // file-based.
122            MappingName::File(filename.into_mapping(None)?),
123        )
124    }
125
126    fn write(
127        &self,
128        _file: &FileObject,
129        _current_task: &CurrentTask,
130        _offset: usize,
131        data: &mut dyn InputBuffer,
132    ) -> Result<usize, Errno> {
133        Ok(data.drain())
134    }
135
136    fn read(
137        &self,
138        _file: &FileObject,
139        _current_task: &CurrentTask,
140        _offset: usize,
141        data: &mut dyn OutputBuffer,
142    ) -> Result<usize, Errno> {
143        data.zero()
144    }
145}
146
147#[derive(Default)]
148struct DevFull;
149impl FileOps for DevFull {
150    fileops_impl_seekless!();
151    fileops_impl_noop_sync!();
152
153    fn write(
154        &self,
155        _file: &FileObject,
156        _current_task: &CurrentTask,
157        _offset: usize,
158        _data: &mut dyn InputBuffer,
159    ) -> Result<usize, Errno> {
160        error!(ENOSPC)
161    }
162
163    fn read(
164        &self,
165        _file: &FileObject,
166        _current_task: &CurrentTask,
167        _offset: usize,
168        data: &mut dyn OutputBuffer,
169    ) -> Result<usize, Errno> {
170        data.write_each(&mut |bytes| {
171            bytes.fill(MaybeUninit::new(0));
172            Ok(bytes.len())
173        })
174    }
175}
176
177#[derive(Default)]
178pub struct DevRandom;
179impl FileOps for DevRandom {
180    fileops_impl_seekless!();
181    fileops_impl_noop_sync!();
182
183    fn write(
184        &self,
185        _file: &FileObject,
186        _current_task: &CurrentTask,
187        _offset: usize,
188        data: &mut dyn InputBuffer,
189    ) -> Result<usize, Errno> {
190        Ok(data.drain())
191    }
192
193    fn read(
194        &self,
195        _file: &FileObject,
196        _current_task: &CurrentTask,
197        _offset: usize,
198        data: &mut dyn OutputBuffer,
199    ) -> Result<usize, Errno> {
200        let mut rdm = vec![0u8; data.available()];
201        starnix_crypto::cprng_draw(&mut rdm);
202        data.write(&rdm)
203    }
204
205    fn ioctl(
206        &self,
207        _file: &FileObject,
208        current_task: &CurrentTask,
209        request: u32,
210        arg: starnix_syscalls::SyscallArg,
211    ) -> Result<starnix_syscalls::SyscallResult, Errno> {
212        match request {
213            starnix_uapi::RNDGETENTCNT => {
214                let addr = starnix_uapi::user_address::UserRef::<i32>::new(UserAddress::from(arg));
215                // Linux just returns 256 no matter what (as observed on 6.5.6).
216                let result = 256;
217                current_task.write_object(addr, &result).map(|_| starnix_syscalls::SUCCESS)
218            }
219            _ => error!(ENOTTY),
220        }
221    }
222}
223
224pub fn open_kmsg(
225    current_task: &CurrentTask,
226    _id: DeviceId,
227    _node: &NamespaceNode,
228    flags: OpenFlags,
229) -> Result<Box<dyn FileOps>, Errno> {
230    if flags.can_read() {
231        Syslog::validate_access(current_task, SyslogAccess::DevKmsgRead)?;
232    }
233    let subscription = if flags.can_read() {
234        Some(Syslog::snapshot_then_subscribe(current_task.kernel())?.into())
235    } else {
236        None
237    };
238    Ok(Box::new(DevKmsg(subscription)))
239}
240
241struct DevKmsg(Option<LockDepMutex<LogSubscription, DevKmsgLock>>);
242
243impl FileOps for DevKmsg {
244    fileops_impl_noop_sync!();
245
246    fn has_persistent_offsets(&self) -> bool {
247        false
248    }
249
250    fn is_seekable(&self) -> bool {
251        true
252    }
253
254    fn seek(
255        &self,
256        _file: &crate::vfs::FileObject,
257        current_task: &crate::task::CurrentTask,
258        _current_offset: starnix_uapi::off_t,
259        target: crate::vfs::SeekTarget,
260    ) -> Result<starnix_uapi::off_t, starnix_uapi::errors::Errno> {
261        match target {
262            SeekTarget::Set(0) => {
263                let Some(ref subscription) = self.0 else {
264                    return Ok(0);
265                };
266                let mut guard = subscription.lock();
267                *guard = Syslog::snapshot_then_subscribe(current_task.kernel())?;
268                Ok(0)
269            }
270            SeekTarget::End(0) => {
271                let Some(ref subscription) = self.0 else {
272                    return Ok(0);
273                };
274                let mut guard = subscription.lock();
275                *guard = Syslog::subscribe(current_task.kernel())?;
276                Ok(0)
277            }
278            SeekTarget::Data(0) => {
279                track_stub!(TODO("https://fxbug.dev/322874315"), "/dev/kmsg: SEEK_DATA");
280                Ok(0)
281            }
282            // The following are implemented as documented on:
283            // https://www.kernel.org/doc/Documentation/ABI/testing/dev-kmsg
284            // The only accepted seek targets are "SEEK_END,0", "SEEK_SET,0" and "SEEK_DATA,0"
285            // When given an invalid offset, ESPIPE is expected.
286            SeekTarget::End(_) | SeekTarget::Set(_) | SeekTarget::Data(_) => {
287                error!(ESPIPE, "Unsupported offset")
288            }
289            // According to the docs above and observations, this should be EINVAL, but dprintf
290            // fails if we make it EINVAL.
291            SeekTarget::Cur(_) => error!(ESPIPE),
292            SeekTarget::Hole(_) => error!(EINVAL, "Unsupported seek target"),
293        }
294    }
295
296    fn wait_async(
297        &self,
298        _file: &FileObject,
299        _current_task: &CurrentTask,
300        waiter: &Waiter,
301        events: FdEvents,
302        handler: EventHandler,
303    ) -> Option<WaitCanceler> {
304        self.0.as_ref().map(|subscription| subscription.lock().wait(waiter, events, handler))
305    }
306
307    fn query_events(
308        &self,
309        _file: &FileObject,
310        _current_task: &CurrentTask,
311    ) -> Result<FdEvents, Errno> {
312        let mut events = FdEvents::empty();
313        if let Some(subscription) = self.0.as_ref() {
314            if subscription.lock().available()? > 0 {
315                events |= FdEvents::POLLIN;
316            }
317        }
318        Ok(events)
319    }
320
321    fn read(
322        &self,
323        file: &FileObject,
324        current_task: &CurrentTask,
325        _offset: usize,
326        data: &mut dyn OutputBuffer,
327    ) -> Result<usize, Errno> {
328        file.blocking_op(current_task, FdEvents::POLLIN | FdEvents::POLLHUP, None, || {
329            match self.0.as_ref().unwrap().lock().next() {
330                Some(Ok(log)) => data.write(&log),
331                Some(Err(err)) => Err(err),
332                None => Ok(0),
333            }
334        })
335    }
336
337    fn write(
338        &self,
339        _file: &FileObject,
340        _current_task: &CurrentTask,
341        _offset: usize,
342        data: &mut dyn InputBuffer,
343    ) -> Result<usize, Errno> {
344        let bytes = data.read_all()?;
345        let extract_result = syslog::extract_level(&bytes);
346        let (level, msg_bytes) = match extract_result {
347            None => (Level::Info, bytes.as_slice()),
348            Some((level, bytes_after_level)) => match level {
349                // An error but keep the <level> str.
350                KmsgLevel::Emergency | KmsgLevel::Alert | KmsgLevel::Critical => {
351                    (Level::Error, bytes.as_slice())
352                }
353                KmsgLevel::Error => (Level::Error, bytes_after_level),
354                KmsgLevel::Warning => (Level::Warn, bytes_after_level),
355                // Log as info but show the <level>.
356                KmsgLevel::Notice => (Level::Info, bytes.as_slice()),
357                KmsgLevel::Info => (Level::Info, bytes_after_level),
358                KmsgLevel::Debug => (Level::Debug, bytes_after_level),
359            },
360        };
361
362        // We need to create and emit our own log record here, because the log macros will include
363        // a file and line by default if the log message is ERROR level. This file/line is not
364        // relevant to log messages forwarded from userspace, and the kmsg tag is hopefully enough
365        // to distinguish messages forwarded this way.
366        starnix_logging::with_current_task_info(|info| {
367            starnix_logging::logger().log(
368                // The log::RecordBuilder API only allows providing the body of a log message as
369                // format_args!(), which cannot be assigned to bindings if it captures values
370                // (https://doc.rust-lang.org/std/macro.format_args.html#lifetime-limitation).
371                // So this creates the record in the same expression where it is used.
372                &starnix_logging::Record::builder()
373                    .level(level)
374                    .key_values(&[
375                        ("tag", LogOutputTag::Str("kmsg")),
376                        ("tag", LogOutputTag::Display(info)),
377                    ])
378                    .args(format_args!(
379                        "{}",
380                        String::from_utf8_lossy(msg_bytes).trim_end_matches('\n')
381                    ))
382                    .build(),
383            );
384        });
385        Ok(bytes.len())
386    }
387}
388
389enum LogOutputTag<'a> {
390    Str(&'a str),
391    Display(&'a dyn std::fmt::Display),
392}
393
394impl<'a> starnix_logging::ToValue for LogOutputTag<'a> {
395    fn to_value(&self) -> starnix_logging::Value<'_> {
396        match self {
397            Self::Str(s) => starnix_logging::Value::from_display(s),
398            Self::Display(d) => starnix_logging::Value::from_dyn_display(d),
399        }
400    }
401}
402
403pub fn mem_device_init<'a>(kernel: &Kernel) -> Result<(), Errno> {
404    let registry = &kernel.device_registry;
405
406    let mem_class = registry.objects.mem_class();
407    registry.register_device(
408        kernel,
409        "null".into(),
410        DeviceMetadata::new("null".into(), DeviceId::NULL, DeviceMode::Char),
411        mem_class.clone(),
412        simple_device_ops::<DevNull>,
413    )?;
414    registry.register_device(
415        kernel,
416        "zero".into(),
417        DeviceMetadata::new("zero".into(), DeviceId::ZERO, DeviceMode::Char),
418        mem_class.clone(),
419        simple_device_ops::<DevZero>,
420    )?;
421    registry.register_device(
422        kernel,
423        "full".into(),
424        DeviceMetadata::new("full".into(), DeviceId::FULL, DeviceMode::Char),
425        mem_class.clone(),
426        simple_device_ops::<DevFull>,
427    )?;
428    registry.register_device(
429        kernel,
430        "random".into(),
431        DeviceMetadata::new("random".into(), DeviceId::RANDOM, DeviceMode::Char),
432        mem_class.clone(),
433        simple_device_ops::<DevRandom>,
434    )?;
435    registry.register_device(
436        kernel,
437        "urandom".into(),
438        DeviceMetadata::new("urandom".into(), DeviceId::URANDOM, DeviceMode::Char),
439        mem_class.clone(),
440        simple_device_ops::<DevRandom>,
441    )?;
442    registry.register_device(
443        kernel,
444        "kmsg".into(),
445        DeviceMetadata::new("kmsg".into(), DeviceId::KMSG, DeviceMode::Char),
446        mem_class,
447        open_kmsg,
448    )?;
449    Ok(())
450}