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