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