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