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