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}