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