starnix_core/vfs/pseudo/
dynamic_file.rs

1// Copyright 2023 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::task::CurrentTask;
6use crate::vfs::buffers::{InputBuffer, OutputBuffer, VecOutputBuffer};
7use crate::vfs::pseudo::simple_file::SimpleFileNode;
8use crate::vfs::{
9    Buffer, FileObject, FileOps, FsNodeOps, OutputBufferCallback, PeekBufferSegmentsCallback,
10    SeekTarget, default_seek, fileops_impl_delegate_read_and_seek, fileops_impl_noop_sync,
11};
12use starnix_sync::{FileOpsCore, Locked, Mutex};
13use starnix_uapi::errors::Errno;
14use starnix_uapi::{errno, error, off_t};
15use std::collections::VecDeque;
16
17unsafe extern "C" {
18    // Declare a symbol that doesn't exist. If the compiler cannot prove that this is never used,
19    // this will create a compilation error showing an issue with the usage of the traits in this
20    // file.
21    fn undefined_symbol_to_prevent_compilation();
22}
23
24pub trait SequenceFileSource: Send + Sync + 'static {
25    type Cursor: Default + Send;
26    fn next(
27        &self,
28        _current_task: &CurrentTask,
29        _cursor: Self::Cursor,
30        _sink: &mut DynamicFileBuf,
31    ) -> Result<Option<Self::Cursor>, Errno> {
32        // SAFETY: This cannot compile and ensure this method is never reached
33        unsafe {
34            undefined_symbol_to_prevent_compilation();
35        }
36        panic!("Either next or next_locked must be implemented");
37    }
38    fn next_locked(
39        &self,
40        _locked: &mut Locked<FileOpsCore>,
41        current_task: &CurrentTask,
42        cursor: Self::Cursor,
43        sink: &mut DynamicFileBuf,
44    ) -> Result<Option<Self::Cursor>, Errno> {
45        self.next(current_task, cursor, sink)
46    }
47}
48
49pub trait DynamicFileSource: Send + Sync + 'static {
50    fn generate(
51        &self,
52        _current_task: &CurrentTask,
53        _sink: &mut DynamicFileBuf,
54    ) -> Result<(), Errno> {
55        // SAFETY: This cannot compile and ensure this method is never reached
56        unsafe {
57            undefined_symbol_to_prevent_compilation();
58        }
59        panic!("Either generate or generate_locked must be implemented");
60    }
61    fn generate_locked(
62        &self,
63        _locked: &mut Locked<FileOpsCore>,
64        current_task: &CurrentTask,
65        sink: &mut DynamicFileBuf,
66    ) -> Result<(), Errno> {
67        self.generate(current_task, sink)
68    }
69}
70
71impl<T> SequenceFileSource for T
72where
73    T: DynamicFileSource,
74{
75    type Cursor = ();
76    fn next_locked(
77        &self,
78        locked: &mut Locked<FileOpsCore>,
79        current_task: &CurrentTask,
80        _cursor: (),
81        sink: &mut DynamicFileBuf,
82    ) -> Result<Option<()>, Errno> {
83        self.generate_locked(locked, current_task, sink).map(|_| None)
84    }
85}
86
87/// `DynamicFile` implements `FileOps` for files whose contents are generated by the kernel
88/// dynamically either from a sequence (see `SequenceFileSource`) or as a single blob of data
89/// (see `DynamicFileSource`). The file may be updated dynamically as it's normally expected
90/// for files in `/proc`, e.g. when seeking back from the current position.
91///
92/// The following example shows how `DynamicFile` can be used with a `DynamicFileSource`:
93/// ```
94/// #[derive(Clone)]
95/// pub struct SimpleFile(u32);
96/// impl SimpleFile {
97///     pub fn new_node(param: u32) -> impl FsNodeOps {
98///         DynamicFile::new_node(Self(param))
99///     }
100/// }
101/// impl DynamicFileSource for SimpleFile {
102///     fn generate(&self, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
103///         writeln!(sink, "param: {}", self.0)?
104///         Ok(())
105///     }
106/// }
107/// ```
108///
109/// `SequenceFileSource` should be used to generate file contents from a sequence of objects.
110/// `SequenceFileSource::next()` takes the cursor for the current position, outputs the next
111/// chunk of data in the sequence, and returns the the advanced cursor value. At the start of
112/// iteration, the cursor is `Default::default()`. The end of the sequence is indicated by
113/// returning `None`.
114///
115/// The next example generates the contents from a sequence of integer values:
116/// ```
117/// [#derive(Clone)]
118/// struct IntegersFile;
119/// impl SequenceFileSource for IntegersFile {
120///     type Cursor = usize;
121///     fn next(&self, cursor: usize, sink: &mut DynamicFileBuf) -> Result<Option<usize>, Errno> {
122///         // The cursor starts at i32::default(), which is 0.
123///         writeln!(sink, "{}", cursor)?;
124///         if cursor > 1000 {
125///             // End of the sequence.
126///             return Ok(None);
127///         }
128///         Ok(Some(cursor + 1))
129///     }
130/// }
131/// ```
132///
133/// Writable files should wrap `DynamicFile` and delegate it `read()` and `seek()` using the
134/// `fileops_impl_delegate_read_and_seek!` macro, as shown in the example below:
135/// ```
136/// struct WritableProcFileSource {
137///   data: usize,
138/// }
139/// impl DynamicFileSource for WritableProcFileSource {
140///     fn generate(&self, sink: &mut DynamicFileBuf) -> Result<(), Errno> {
141///         writeln!("{}", self.data);
142///         Ok(())
143///     }
144/// }
145/// pub struct WritableProcFile {
146///   dynamic_file: DynamicFile<WritableProcFileSource>,
147///   ...
148/// }
149/// impl WritableProcFile {
150///     fn new() -> Self {
151///         Self { dynamic_file: DynamicFile::new(WritableProcFileSource { data: 42 }) }
152///     }
153/// }
154/// impl FileOps for WritableProcFile {
155///     fileops_impl_delegate_read_and_seek!(self, self.dynamic_file);
156///
157///     fn write(
158///         &self,
159///         _file: &FileObject,
160///         _current_task: &CurrentTask,
161///         _offset: usize,
162///         data: &mut dyn InputBuffer,
163///     ) -> Result<usize, Errno> {
164///         ... Process write() ...
165///     }
166/// }
167/// ```
168///
169pub struct DynamicFile<Source: SequenceFileSource> {
170    state: Mutex<DynamicFileState<Source>>,
171}
172
173impl<Source: SequenceFileSource> DynamicFile<Source> {
174    pub fn new(source: Source) -> Self {
175        DynamicFile { state: Mutex::new(DynamicFileState::new(source)) }
176    }
177}
178
179impl<Source: SequenceFileSource + Clone> DynamicFile<Source> {
180    pub fn new_node(source: Source) -> impl FsNodeOps {
181        SimpleFileNode::new(move || Ok(DynamicFile::new(source.clone())))
182    }
183}
184
185impl<Source: SequenceFileSource> DynamicFile<Source> {
186    fn read_internal(
187        &self,
188        locked: &mut Locked<FileOpsCore>,
189        current_task: &CurrentTask,
190        offset: usize,
191        data: &mut dyn OutputBuffer,
192    ) -> Result<usize, Errno> {
193        self.state.lock().read(locked, current_task, offset, data)
194    }
195}
196
197impl<Source: SequenceFileSource> FileOps for DynamicFile<Source> {
198    fileops_impl_noop_sync!();
199
200    fn is_seekable(&self) -> bool {
201        true
202    }
203
204    fn read(
205        &self,
206        locked: &mut Locked<FileOpsCore>,
207        _file: &FileObject,
208        current_task: &CurrentTask,
209        offset: usize,
210        data: &mut dyn OutputBuffer,
211    ) -> Result<usize, Errno> {
212        self.read_internal(locked, current_task, offset, data)
213    }
214
215    fn write(
216        &self,
217        _locked: &mut Locked<FileOpsCore>,
218        _file: &FileObject,
219        _current_task: &CurrentTask,
220        _offset: usize,
221        _data: &mut dyn InputBuffer,
222    ) -> Result<usize, Errno> {
223        error!(ENOSYS)
224    }
225
226    fn seek(
227        &self,
228        locked: &mut Locked<FileOpsCore>,
229        _file: &FileObject,
230        current_task: &CurrentTask,
231        current_offset: off_t,
232        target: SeekTarget,
233    ) -> Result<off_t, Errno> {
234        let new_offset = default_seek(current_offset, target, || error!(EINVAL))?;
235
236        // Call `read(0)` to ensure the data is generated now instead of later (except, when
237        // seeking to the start of the file).
238        if new_offset > 0 {
239            let mut dummy_buf = VecOutputBuffer::new(0);
240            self.read_internal(locked, current_task, new_offset as usize, &mut dummy_buf)?;
241        }
242
243        Ok(new_offset)
244    }
245}
246
247/// Internal state of a `DynamicFile`.
248struct DynamicFileState<Source: SequenceFileSource> {
249    /// The `Source` that's used to generate content of the file.
250    source: Source,
251
252    /// The current position in the sequence. This is an opaque object. Stepping the iterator
253    /// replaces it with the next value in the sequence.
254    cursor: Option<Source::Cursor>,
255
256    /// Buffer for upcoming data in the sequence. Read calls will expand this buffer until it is
257    /// big enough and then copy out data from it.
258    buf: DynamicFileBuf,
259
260    /// The current seek offset in the file. The first byte in the buffer is at this offset in the
261    /// file.
262    ///
263    /// If a read has an offset greater than this, bytes will be generated from the iterator
264    /// and skipped. If a read has an offset less than this, all state is reset and iteration
265    /// starts from the beginning until it reaches the requested offset.
266    byte_offset: usize,
267}
268
269impl<Source: SequenceFileSource> DynamicFileState<Source> {
270    fn new(source: Source) -> Self {
271        Self {
272            source,
273            cursor: Some(Source::Cursor::default()),
274            buf: DynamicFileBuf::default(),
275            byte_offset: 0,
276        }
277    }
278}
279
280impl<Source: SequenceFileSource> DynamicFileState<Source> {
281    fn reset(&mut self) {
282        self.cursor = Some(Source::Cursor::default());
283        self.buf = DynamicFileBuf::default();
284        self.byte_offset = 0;
285    }
286
287    fn read(
288        &mut self,
289        locked: &mut Locked<FileOpsCore>,
290        current_task: &CurrentTask,
291        offset: usize,
292        data: &mut dyn OutputBuffer,
293    ) -> Result<usize, Errno> {
294        if offset != self.byte_offset {
295            self.reset();
296        }
297        let read_size = data.available();
298
299        // 1. Grow the buffer until either EOF or it's at least as big as the read request
300        while self.byte_offset + self.buf.0.len() < offset + read_size {
301            let cursor = if let Some(cursor) = std::mem::take(&mut self.cursor) {
302                cursor
303            } else {
304                break;
305            };
306            let mut buf = std::mem::take(&mut self.buf);
307            self.cursor =
308                self.source.next_locked(locked, current_task, cursor, &mut buf).map_err(|e| {
309                    // Reset everything on failure
310                    self.reset();
311                    e
312                })?;
313            self.buf = buf;
314
315            // If the seek pointer is ahead of our current byte offset, we will generate data that
316            // needs to be thrown away. Calculation for that is here.
317            let to_drain = std::cmp::min(offset - self.byte_offset, self.buf.0.len());
318            self.buf.0.drain(..to_drain);
319            self.byte_offset += to_drain;
320        }
321
322        // 2. Copy out as much of the data as possible. `write()` may need to be called twice
323        // because `VecDeque` keeps the data in a ring buffer.
324        let (slice1, slice2) = self.buf.0.as_slices();
325        let mut written = data.write(slice1)?;
326        if written == slice1.len() && !slice2.is_empty() {
327            written += data.write(slice2)?;
328        }
329
330        // 3. Move the current position and drop the consumed data.
331        self.buf.0.drain(..written);
332        self.byte_offset += written;
333        Ok(written)
334    }
335}
336
337#[derive(Debug, Default)]
338pub struct DynamicFileBuf(VecDeque<u8>);
339impl DynamicFileBuf {
340    pub fn write(&mut self, data: &[u8]) {
341        self.0.extend(data.iter().copied());
342    }
343    pub fn write_iter<I>(&mut self, data: I)
344    where
345        I: IntoIterator<Item = u8>,
346    {
347        self.0.extend(data);
348    }
349    pub fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<usize, Errno> {
350        let start_size = self.0.len();
351        std::io::Write::write_fmt(&mut self.0, args).map_err(|_| errno!(EINVAL))?;
352        let end_size = self.0.len();
353        Ok(end_size - start_size)
354    }
355}
356
357impl Buffer for DynamicFileBuf {
358    fn segments_count(&self) -> Result<usize, Errno> {
359        std::unimplemented!();
360    }
361
362    fn peek_each_segment(
363        &mut self,
364        _callback: &mut PeekBufferSegmentsCallback<'_>,
365    ) -> Result<(), Errno> {
366        std::unimplemented!();
367    }
368}
369
370impl OutputBuffer for DynamicFileBuf {
371    fn available(&self) -> usize {
372        std::unimplemented!();
373    }
374
375    fn bytes_written(&self) -> usize {
376        std::unimplemented!();
377    }
378
379    fn zero(&mut self) -> Result<usize, Errno> {
380        std::unimplemented!();
381    }
382
383    fn write_each(&mut self, _callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno> {
384        std::unimplemented!();
385    }
386
387    fn write_all(&mut self, buffer: &[u8]) -> Result<usize, Errno> {
388        self.write(buffer);
389        Ok(buffer.len())
390    }
391
392    unsafe fn advance(&mut self, _length: usize) -> Result<(), Errno> {
393        std::unimplemented!();
394    }
395}
396
397/// A file whose contents are fixed even if writes occur.
398pub struct ConstFile(DynamicFile<ConstFileSource>);
399
400struct ConstFileSource {
401    data: Vec<u8>,
402}
403
404impl DynamicFileSource for ConstFileSource {
405    fn generate(
406        &self,
407        _current_task: &CurrentTask,
408        sink: &mut DynamicFileBuf,
409    ) -> Result<(), Errno> {
410        sink.write(&self.data);
411        Ok(())
412    }
413}
414
415impl ConstFile {
416    /// Create a file with the given contents.
417    pub fn new_node(data: Vec<u8>) -> impl FsNodeOps {
418        SimpleFileNode::new(move || {
419            Ok(Self(DynamicFile::new(ConstFileSource { data: data.clone() })))
420        })
421    }
422}
423
424impl FileOps for ConstFile {
425    fileops_impl_delegate_read_and_seek!(self, self.0);
426    fileops_impl_noop_sync!();
427
428    fn write(
429        &self,
430        _locked: &mut Locked<FileOpsCore>,
431        _file: &FileObject,
432        _current_task: &CurrentTask,
433        _offset: usize,
434        data: &mut dyn InputBuffer,
435    ) -> Result<usize, Errno> {
436        Ok(data.drain())
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use crate::task::CurrentTask;
443    use crate::testing::{anon_test_file, spawn_kernel_and_run};
444    use crate::vfs::pseudo::dynamic_file::{
445        DynamicFile, DynamicFileBuf, DynamicFileSource, SequenceFileSource,
446    };
447    use crate::vfs::{SeekTarget, VecOutputBuffer};
448    use starnix_sync::{Locked, Mutex, Unlocked};
449    use starnix_uapi::errors::Errno;
450    use starnix_uapi::open_flags::OpenFlags;
451    use std::sync::Arc;
452
453    struct Counter {
454        value: Mutex<u8>,
455    }
456
457    struct TestSequenceFileSource;
458
459    impl SequenceFileSource for TestSequenceFileSource {
460        type Cursor = u8;
461        fn next(
462            &self,
463            _current_task: &CurrentTask,
464            i: u8,
465            sink: &mut DynamicFileBuf,
466        ) -> Result<Option<u8>, Errno> {
467            sink.write(&[i]);
468            Ok(if i == u8::MAX { None } else { Some(i + 1) })
469        }
470    }
471
472    #[fuchsia::test]
473    async fn test_sequence() {
474        spawn_kernel_and_run(async |locked, current_task| {
475            let file = anon_test_file(
476                locked,
477                &current_task,
478                Box::new(DynamicFile::new(TestSequenceFileSource {})),
479                OpenFlags::RDONLY,
480            );
481
482            let read_at = |locked: &mut Locked<Unlocked>,
483                           offset: usize,
484                           length: usize|
485             -> Result<Vec<u8>, Errno> {
486                let mut buffer = VecOutputBuffer::new(length);
487                file.read_at(locked, &current_task, offset, &mut buffer)?;
488                Ok(buffer.data().to_vec())
489            };
490
491            assert_eq!(read_at(locked, 0, 2).unwrap(), &[0, 1]);
492            assert_eq!(read_at(locked, 2, 2).unwrap(), &[2, 3]);
493            assert_eq!(read_at(locked, 4, 4).unwrap(), &[4, 5, 6, 7]);
494            assert_eq!(read_at(locked, 0, 2).unwrap(), &[0, 1]);
495            assert_eq!(read_at(locked, 4, 2).unwrap(), &[4, 5]);
496        })
497        .await;
498    }
499
500    struct TestFileSource {
501        counter: Arc<Counter>,
502    }
503
504    impl DynamicFileSource for TestFileSource {
505        fn generate(
506            &self,
507            _current_task: &CurrentTask,
508            sink: &mut DynamicFileBuf,
509        ) -> Result<(), Errno> {
510            let mut counter = self.counter.value.lock();
511            let base = *counter;
512            // Write 10 bytes where v[i] = base + i.
513            let data = (0..10).map(|i| base + i).collect::<Vec<u8>>();
514            sink.write(&data);
515            *counter += 1;
516            Ok(())
517        }
518    }
519
520    #[fuchsia::test]
521    async fn test_read() {
522        let counter = Arc::new(Counter { value: Mutex::new(0) });
523        spawn_kernel_and_run(async move |locked, current_task| {
524            let file = anon_test_file(
525                locked,
526                &current_task,
527                Box::new(DynamicFile::new(TestFileSource { counter: counter.clone() })),
528                OpenFlags::RDONLY,
529            );
530            let read_at = |locked: &mut Locked<Unlocked>,
531                           offset: usize,
532                           length: usize|
533             -> Result<Vec<u8>, Errno> {
534                let mut buffer = VecOutputBuffer::new(length);
535                let bytes_read = file.read_at(locked, &current_task, offset, &mut buffer)?;
536                Ok(buffer.data()[0..bytes_read].to_vec())
537            };
538
539            // Verify that we can read all data to the end.
540            assert_eq!(read_at(locked, 0, 20).unwrap(), (0..10).collect::<Vec<u8>>());
541
542            // Read from the beginning. Content should be refreshed.
543            assert_eq!(read_at(locked, 0, 2).unwrap(), [1, 2]);
544
545            // Continue reading. Content should not be updated.
546            assert_eq!(read_at(locked, 2, 2).unwrap(), [3, 4]);
547
548            // Try reading from a new position. Content should be updated.
549            assert_eq!(read_at(locked, 5, 2).unwrap(), [7, 8]);
550        })
551        .await;
552    }
553
554    #[fuchsia::test]
555    async fn test_read_and_seek() {
556        let counter = Arc::new(Counter { value: Mutex::new(0) });
557        spawn_kernel_and_run(async move |locked, current_task| {
558            let file = anon_test_file(
559                locked,
560                &current_task,
561                Box::new(DynamicFile::new(TestFileSource { counter: counter.clone() })),
562                OpenFlags::RDONLY,
563            );
564            let read = |locked: &mut Locked<Unlocked>, length: usize| -> Result<Vec<u8>, Errno> {
565                let mut buffer = VecOutputBuffer::new(length);
566                let bytes_read = file.read(locked, &current_task, &mut buffer)?;
567                Ok(buffer.data()[0..bytes_read].to_vec())
568            };
569
570            // Call `read()` to read the content all the way to the end. Content should not update
571            assert_eq!(read(locked, 1).unwrap(), [0]);
572            assert_eq!(read(locked, 2).unwrap(), [1, 2]);
573            assert_eq!(read(locked, 20).unwrap(), (3..10).collect::<Vec<u8>>());
574
575            // Seek to the start of the file. Content should be updated on the following read.
576            file.seek(locked, &current_task, SeekTarget::Set(0)).unwrap();
577            assert_eq!(*counter.value.lock(), 1);
578            assert_eq!(read(locked, 2).unwrap(), [1, 2]);
579            assert_eq!(*counter.value.lock(), 2);
580
581            // Seeking to `pos > 0` should update the content.
582            file.seek(locked, &current_task, SeekTarget::Set(1)).unwrap();
583            assert_eq!(*counter.value.lock(), 3);
584        })
585        .await;
586    }
587}