Skip to main content

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