Skip to main content

libc/stdio/
string_file.rs

1// Copyright 2021 The Fuchsia Authors
2// Copyright (c) 2008 Travis Geiselbrecht
3//
4// Use of this source code is governed by a MIT-style
5// license that can be found in the LICENSE file or at
6// https://opensource.org/licenses/MIT
7
8use core::ffi::{c_char, c_int, c_void};
9use core::fmt;
10use core::marker::PhantomData;
11
12use super::FILE;
13
14/// `FILE*` wrapper over a string.
15///
16/// The last byte of the buffer is always reserved for a null character, which is only written by
17/// [`StringFile::take`].
18#[derive(Debug)]
19pub struct StringFile<'a> {
20    buffer: &'a mut [u8],
21    offset: usize,
22}
23
24impl<'a> StringFile<'a> {
25    /// Creates a `StringFile` that writes into `buffer`.
26    pub fn new(buffer: &'a mut [u8]) -> Self {
27        Self { buffer, offset: 0 }
28    }
29
30    /// Adds a null character at the end of the current buffer and returns a view into the written
31    /// section.
32    pub fn take(self) -> &'a mut [u8] {
33        if self.buffer.is_empty() {
34            return &mut [];
35        }
36        self.buffer[self.offset] = 0;
37        &mut self.buffer[..self.offset + 1]
38    }
39
40    /// Returns `bytes.len()` and writes as much of `bytes` as it would fit in the buffer
41    /// `[offset, size - 1)` while reserving the last byte for null character.
42    // Kept out of line so that callers don't each get a copy of the bounds checks.
43    #[inline(never)]
44    pub fn write(&mut self, bytes: &[u8]) -> usize {
45        if self.buffer.is_empty() {
46            return bytes.len();
47        }
48        // Last element is reserved for '\0' character.
49        let count = bytes.len().min(self.buffer.len() - self.offset - 1);
50        self.buffer[self.offset..self.offset + count].copy_from_slice(&bytes[..count]);
51        self.offset += count;
52        bytes.len()
53    }
54
55    /// Returns a region representing the currently used portion of the buffer.
56    pub fn used_region(&self) -> &[u8] {
57        &self.buffer[..self.offset]
58    }
59
60    /// Returns a region representing the remaining unused space in the buffer, _not_ including the
61    /// space reserved for the final null character.
62    pub fn available_region(&mut self) -> &mut [u8] {
63        let available = (self.buffer.len() - self.offset).saturating_sub(1);
64        &mut self.buffer[self.offset..self.offset + available]
65    }
66
67    /// Skip up to `amount` bytes in the buffer, advancing the write pointer as if we had written
68    /// data, but without actually changing the buffer.
69    pub fn skip(&mut self, amount: usize) {
70        let amount = amount.min(self.available_region().len());
71        self.offset += amount;
72    }
73
74    /// Returns the currently used portion of the buffer, like C++ `as_string_view()`.
75    pub fn as_bytes(&self) -> &[u8] {
76        self.used_region()
77    }
78
79    /// Calls `f` with a C++-compatible `FILE` whose output is written into this `StringFile`.
80    ///
81    /// This lets C++ routines that render into a `FILE*` write into a Rust `StringFile`.
82    pub fn with_file<R>(&mut self, f: impl for<'f> FnOnce(&mut ScopedFile<'f>) -> R) -> R {
83        // SAFETY: `file_write` is sound to call with a pointer to this `StringFile` and any
84        // readable `(data, len)`. The `FILE` is only reachable through the `ScopedFile` lent to
85        // `f`, which safe code can neither move out of nor swap with another `ScopedFile`, so the
86        // `FILE` cannot outlive this call, during which `self` is mutably borrowed and the pointer
87        // stays valid and unaliased.
88        let file =
89            unsafe { FILE::new(Some(Self::file_write), (self as *mut Self).cast::<c_void>()) };
90        f(&mut ScopedFile { file, _brand: PhantomData })
91    }
92
93    /// The `FILE` callback installed by [`StringFile::with_file`].
94    ///
95    /// # Safety
96    ///
97    /// `ptr` must point to a live `StringFile` that is not otherwise accessed during the call, and
98    /// `data` must point to `len` readable bytes when `len > 0`.
99    unsafe extern "C" fn file_write(ptr: *mut c_void, data: *const c_char, len: usize) -> c_int {
100        // SAFETY: `with_file` passes a pointer to the `StringFile` it mutably borrows for the
101        // lifetime of the `FILE`, so no other access can happen while the callback runs.
102        let this = unsafe { &mut *ptr.cast::<StringFile<'_>>() };
103        let bytes = if len == 0 {
104            &[]
105        } else {
106            // SAFETY: `len > 0` and the caller guarantees `data` points to `len` readable bytes.
107            unsafe { core::slice::from_raw_parts(data.cast::<u8>(), len) }
108        };
109        this.write(bytes) as c_int
110    }
111}
112
113impl AsRef<[u8]> for StringFile<'_> {
114    fn as_ref(&self) -> &[u8] {
115        self.as_bytes()
116    }
117}
118
119impl fmt::Write for StringFile<'_> {
120    fn write_str(&mut self, s: &str) -> fmt::Result {
121        self.write(s.as_bytes());
122        Ok(())
123    }
124}
125
126/// The `FILE` lent to the closure passed to [`StringFile::with_file`].
127///
128/// The `FILE` points back at its `StringFile`, so it must not outlive the `with_file` call. Safe
129/// code only ever gets a `&mut ScopedFile<'f>` whose `'f` is unique to one `with_file` call, and
130/// can never get a `&mut FILE`, so the `FILE` can be neither moved out nor swapped with the `FILE`
131/// of another (possibly shorter-lived) `StringFile`.
132pub struct ScopedFile<'f> {
133    file: FILE,
134    // Makes `ScopedFile` invariant in `'f`, so `ScopedFile`s from different `with_file` calls have
135    // distinct types and cannot be swapped.
136    _brand: PhantomData<fn(&'f ()) -> &'f ()>,
137}
138
139impl ScopedFile<'_> {
140    /// Writes `bytes` to the `StringFile`, like [`FILE::write`].
141    pub fn write(&self, bytes: &[u8]) -> c_int {
142        self.file.write(bytes)
143    }
144
145    /// Returns a pointer to the `FILE`, for passing to C++ routines that take a `FILE*`.
146    ///
147    /// The pointer must not be used after the `with_file` closure returns.
148    pub fn as_mut_ptr(&mut self) -> *mut FILE {
149        &raw mut self.file
150    }
151}
152
153impl fmt::Write for ScopedFile<'_> {
154    fn write_str(&mut self, s: &str) -> fmt::Result {
155        fmt::Write::write_str(&mut self.file, s)
156    }
157}