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::{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        default_seek(current_offset, target, || error!(EINVAL))
262    }
263}
264
265/// Internal state of a `DynamicFile`.
266struct DynamicFileState<Source: SequenceFileSource> {
267    /// The `Source` that's used to generate content of the file.
268    source: Source,
269
270    /// The current position in the sequence. This is an opaque object. Stepping the iterator
271    /// replaces it with the next value in the sequence.
272    cursor: Option<Source::Cursor>,
273
274    /// Buffer for upcoming data in the sequence. Read calls will expand this buffer until it is
275    /// big enough and then copy out data from it.
276    buf: DynamicFileBuf,
277
278    /// The current seek offset in the file. The first byte in the buffer is at this offset in the
279    /// file.
280    ///
281    /// If a read has an offset greater than this, bytes will be generated from the iterator
282    /// and skipped. If a read has an offset less than this, all state is reset and iteration
283    /// starts from the beginning until it reaches the requested offset.
284    byte_offset: usize,
285}
286
287impl<Source: SequenceFileSource> DynamicFileState<Source> {
288    fn new(source: Source) -> Self {
289        Self {
290            source,
291            cursor: Some(Source::Cursor::default()),
292            buf: DynamicFileBuf::default(),
293            byte_offset: 0,
294        }
295    }
296}
297
298impl<Source: SequenceFileSource> DynamicFileState<Source> {
299    fn reset(&mut self) {
300        self.cursor = Some(Source::Cursor::default());
301        self.buf = DynamicFileBuf::default();
302        self.byte_offset = 0;
303    }
304
305    fn read(
306        &mut self,
307        locked: &mut Locked<FileOpsCore>,
308        current_task: &CurrentTask,
309        offset: usize,
310        data: &mut dyn OutputBuffer,
311    ) -> Result<usize, Errno> {
312        if offset != self.byte_offset {
313            self.reset();
314        }
315        let read_size = data.available();
316
317        // 1. Grow the buffer until either EOF or it's at least as big as the read request
318        while self.byte_offset + self.buf.0.len() < offset + read_size {
319            let cursor = if let Some(cursor) = std::mem::take(&mut self.cursor) {
320                cursor
321            } else {
322                break;
323            };
324            let mut buf = std::mem::take(&mut self.buf);
325            self.cursor =
326                self.source.next_locked(locked, current_task, cursor, &mut buf).map_err(|e| {
327                    // Reset everything on failure
328                    self.reset();
329                    e
330                })?;
331            self.buf = buf;
332
333            // If the seek pointer is ahead of our current byte offset, we will generate data that
334            // needs to be thrown away. Calculation for that is here.
335            let to_drain = std::cmp::min(offset - self.byte_offset, self.buf.0.len());
336            self.buf.0.drain(..to_drain);
337            self.byte_offset += to_drain;
338        }
339
340        // 2. Copy out as much of the data as possible. `write()` may need to be called twice
341        // because `VecDeque` keeps the data in a ring buffer.
342        let (slice1, slice2) = self.buf.0.as_slices();
343        let mut written = data.write(slice1)?;
344        if written == slice1.len() && !slice2.is_empty() {
345            written += data.write(slice2)?;
346        }
347
348        // 3. Move the current position and drop the consumed data.
349        self.buf.0.drain(..written);
350        self.byte_offset += written;
351        Ok(written)
352    }
353
354    fn write(
355        &mut self,
356        locked: &mut Locked<FileOpsCore>,
357        current_task: &CurrentTask,
358        offset: usize,
359        data: &mut dyn InputBuffer,
360    ) -> Result<usize, Errno> {
361        self.source.write(locked, current_task, offset, data)
362    }
363}
364
365#[derive(Debug, Default)]
366pub struct DynamicFileBuf(VecDeque<u8>);
367impl DynamicFileBuf {
368    pub fn write(&mut self, data: &[u8]) {
369        self.0.extend(data.iter().copied());
370    }
371    pub fn write_iter<I>(&mut self, data: I)
372    where
373        I: IntoIterator<Item = u8>,
374    {
375        self.0.extend(data);
376    }
377    pub fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> Result<usize, Errno> {
378        let start_size = self.0.len();
379        std::io::Write::write_fmt(&mut self.0, args).map_err(|_| errno!(EINVAL))?;
380        let end_size = self.0.len();
381        Ok(end_size - start_size)
382    }
383}
384
385impl Buffer for DynamicFileBuf {
386    fn segments_count(&self) -> Result<usize, Errno> {
387        std::unimplemented!();
388    }
389
390    fn peek_each_segment(
391        &mut self,
392        _callback: &mut PeekBufferSegmentsCallback<'_>,
393    ) -> Result<(), Errno> {
394        std::unimplemented!();
395    }
396}
397
398impl OutputBuffer for DynamicFileBuf {
399    fn available(&self) -> usize {
400        std::unimplemented!();
401    }
402
403    fn bytes_written(&self) -> usize {
404        std::unimplemented!();
405    }
406
407    fn zero(&mut self) -> Result<usize, Errno> {
408        std::unimplemented!();
409    }
410
411    fn write_each(&mut self, _callback: &mut OutputBufferCallback<'_>) -> Result<usize, Errno> {
412        std::unimplemented!();
413    }
414
415    fn write_all(&mut self, buffer: &[u8]) -> Result<usize, Errno> {
416        self.write(buffer);
417        Ok(buffer.len())
418    }
419
420    unsafe fn advance(&mut self, _length: usize) -> Result<(), Errno> {
421        std::unimplemented!();
422    }
423}
424
425/// A file whose contents are fixed even if writes occur.
426pub struct ConstFile {
427    data: Vec<u8>,
428}
429
430impl DynamicFileSource for ConstFile {
431    fn generate(
432        &self,
433        _current_task: &CurrentTask,
434        sink: &mut DynamicFileBuf,
435    ) -> Result<(), Errno> {
436        sink.write(&self.data);
437        Ok(())
438    }
439
440    fn write(
441        &self,
442        _locked: &mut Locked<FileOpsCore>,
443        _current_task: &CurrentTask,
444        _offset: usize,
445        data: &mut dyn InputBuffer,
446    ) -> Result<usize, Errno> {
447        Ok(data.drain())
448    }
449}
450
451impl ConstFile {
452    /// Create a file with the given contents.
453    pub fn new_node(data: Vec<u8>) -> impl FsNodeOps {
454        SimpleFileNode::new(move |_, _| Ok(DynamicFile::new(ConstFile { data: data.clone() })))
455    }
456}
457
458#[cfg(test)]
459mod tests {
460    use crate::task::CurrentTask;
461    use crate::testing::{anon_test_file, spawn_kernel_and_run};
462    use crate::vfs::pseudo::dynamic_file::{
463        DynamicFile, DynamicFileBuf, DynamicFileSource, SequenceFileSource,
464    };
465    use crate::vfs::{SeekTarget, VecOutputBuffer};
466    use starnix_sync::{Locked, Mutex, Unlocked};
467    use starnix_uapi::errors::Errno;
468    use starnix_uapi::open_flags::OpenFlags;
469    use std::sync::Arc;
470
471    struct Counter {
472        value: Mutex<u8>,
473    }
474
475    struct TestSequenceFileSource;
476
477    impl SequenceFileSource for TestSequenceFileSource {
478        type Cursor = u8;
479        fn next(
480            &self,
481            _current_task: &CurrentTask,
482            i: u8,
483            sink: &mut DynamicFileBuf,
484        ) -> Result<Option<u8>, Errno> {
485            sink.write(&[i]);
486            Ok(if i == u8::MAX { None } else { Some(i + 1) })
487        }
488    }
489
490    #[fuchsia::test]
491    async fn test_sequence() {
492        spawn_kernel_and_run(async |locked, current_task| {
493            let file = anon_test_file(
494                locked,
495                &current_task,
496                Box::new(DynamicFile::new(TestSequenceFileSource {})),
497                OpenFlags::RDONLY,
498            );
499
500            let read_at = |locked: &mut Locked<Unlocked>,
501                           offset: usize,
502                           length: usize|
503             -> Result<Vec<u8>, Errno> {
504                let mut buffer = VecOutputBuffer::new(length);
505                file.read_at(locked, &current_task, offset, &mut buffer)?;
506                Ok(buffer.data().to_vec())
507            };
508
509            assert_eq!(read_at(locked, 0, 2).unwrap(), &[0, 1]);
510            assert_eq!(read_at(locked, 2, 2).unwrap(), &[2, 3]);
511            assert_eq!(read_at(locked, 4, 4).unwrap(), &[4, 5, 6, 7]);
512            assert_eq!(read_at(locked, 0, 2).unwrap(), &[0, 1]);
513            assert_eq!(read_at(locked, 4, 2).unwrap(), &[4, 5]);
514        })
515        .await;
516    }
517
518    struct TestFileSource {
519        counter: Arc<Counter>,
520    }
521
522    impl DynamicFileSource for TestFileSource {
523        fn generate(
524            &self,
525            _current_task: &CurrentTask,
526            sink: &mut DynamicFileBuf,
527        ) -> Result<(), Errno> {
528            let mut counter = self.counter.value.lock();
529            let base = *counter;
530            // Write 10 bytes where v[i] = base + i.
531            let data = (0..10).map(|i| base + i).collect::<Vec<u8>>();
532            sink.write(&data);
533            *counter += 1;
534            Ok(())
535        }
536    }
537
538    #[fuchsia::test]
539    async fn test_read() {
540        let counter = Arc::new(Counter { value: Mutex::new(0) });
541        spawn_kernel_and_run(async move |locked, current_task| {
542            let file = anon_test_file(
543                locked,
544                &current_task,
545                Box::new(DynamicFile::new(TestFileSource { counter: counter.clone() })),
546                OpenFlags::RDONLY,
547            );
548            let read_at = |locked: &mut Locked<Unlocked>,
549                           offset: usize,
550                           length: usize|
551             -> Result<Vec<u8>, Errno> {
552                let mut buffer = VecOutputBuffer::new(length);
553                let bytes_read = file.read_at(locked, &current_task, offset, &mut buffer)?;
554                Ok(buffer.data()[0..bytes_read].to_vec())
555            };
556
557            // Verify that we can read all data to the end.
558            assert_eq!(read_at(locked, 0, 20).unwrap(), (0..10).collect::<Vec<u8>>());
559
560            // Read from the beginning. Content should be refreshed.
561            assert_eq!(read_at(locked, 0, 2).unwrap(), [1, 2]);
562
563            // Continue reading. Content should not be updated.
564            assert_eq!(read_at(locked, 2, 2).unwrap(), [3, 4]);
565
566            // Try reading from a new position. Content should be updated.
567            assert_eq!(read_at(locked, 5, 2).unwrap(), [7, 8]);
568        })
569        .await;
570    }
571
572    #[fuchsia::test]
573    async fn test_read_and_seek() {
574        let counter = Arc::new(Counter { value: Mutex::new(0) });
575        spawn_kernel_and_run(async move |locked, current_task| {
576            let file = anon_test_file(
577                locked,
578                &current_task,
579                Box::new(DynamicFile::new(TestFileSource { counter: counter.clone() })),
580                OpenFlags::RDONLY,
581            );
582            let read = |locked: &mut Locked<Unlocked>, length: usize| -> Result<Vec<u8>, Errno> {
583                let mut buffer = VecOutputBuffer::new(length);
584                let bytes_read = file.read(locked, &current_task, &mut buffer)?;
585                Ok(buffer.data()[0..bytes_read].to_vec())
586            };
587
588            // Call `read()` to read the content all the way to the end. Content should not update
589            assert_eq!(read(locked, 1).unwrap(), [0]);
590            assert_eq!(read(locked, 2).unwrap(), [1, 2]);
591            assert_eq!(read(locked, 20).unwrap(), (3..10).collect::<Vec<u8>>());
592
593            // Seek to the start of the file. Content should be updated on the following read.
594            file.seek(locked, &current_task, SeekTarget::Set(0)).unwrap();
595            assert_eq!(*counter.value.lock(), 1);
596            assert_eq!(read(locked, 2).unwrap(), [1, 2]);
597            assert_eq!(*counter.value.lock(), 2);
598
599            // Seeking to `pos > 0` should NOT update the content immediately (lazy seek).
600            file.seek(locked, &current_task, SeekTarget::Set(1)).unwrap();
601            assert_eq!(*counter.value.lock(), 2);
602
603            // Content should be updated on the following read.
604            assert_eq!(read(locked, 1).unwrap(), [3]);
605            assert_eq!(*counter.value.lock(), 3);
606        })
607        .await;
608    }
609}