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