Skip to main content

starnix_c_file_buffer/
lib.rs

1// Copyright 2026 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
5//! C-compatible memory-backed file buffer for FFI.
6
7use std::ffi::CStr;
8use std::marker::PhantomData;
9
10unsafe extern "C" {
11    fn fmemopen(
12        buf: *mut libc::c_void,
13        size: libc::size_t,
14        mode: *const libc::c_char,
15    ) -> *mut libc::FILE;
16}
17
18/// A wrapper around the raw `FILE*` that borrows `CFileBuffer` mutably.
19/// This ensures exclusive access to the file descriptor.
20pub struct CFilePtr<'a> {
21    ptr: *mut libc::FILE,
22    _marker: PhantomData<&'a mut CFileBuffer>,
23}
24
25impl<'a> CFilePtr<'a> {
26    /// Get the raw `FILE*` pointer.
27    ///
28    /// # Safety Constraints
29    /// The returned raw pointer must not be stored or used after this `CFilePtr`
30    /// (or the parent `CFileBuffer`) is dropped. The caller must ensure that the
31    /// parent `CFileBuffer` outlives any use of the raw pointer.
32    pub fn as_raw(&self) -> *mut libc::FILE {
33        self.ptr
34    }
35}
36
37/// A C-compatible memory-backed file buffer.
38///
39/// This struct wraps a `FILE*` created via `fmemopen`, allowing FFI functions
40/// to write to a memory buffer that can then be read from Rust.
41pub struct CFileBuffer {
42    file: *mut libc::FILE,
43    buffer: Vec<u8>,
44}
45
46// SAFETY: CFileBuffer owns the FILE* and the buffer, and does not share them concurrently.
47// It is safe to transfer ownership of CFileBuffer to another thread.
48unsafe impl Send for CFileBuffer {}
49
50impl CFileBuffer {
51    /// Create a new CFileBuffer object with a buffer of `max_size`.
52    pub fn new(max_size: usize) -> Result<Self, &'static str> {
53        let mut buffer = vec![0u8; max_size];
54
55        // Open the file in "w+" mode. We use "w+" instead of "w" because in "w"
56        // mode, musl's fmemopen will overwrite the last byte with a null character
57        // when the buffer is full, corrupting the data if we write exactly `max_size`
58        // bytes. "w+" mode avoids this behavior.
59        let mode = CStr::from_bytes_with_nul(b"w+\0").unwrap();
60
61        // SAFETY: The buffer is owned by the CFileBuffer struct and will remain
62        // valid for the lifetime of the returned FILE*. The mode string is null-terminated.
63        let file =
64            unsafe { fmemopen(buffer.as_mut_ptr() as *mut libc::c_void, max_size, mode.as_ptr()) };
65
66        if file.is_null() {
67            return Err("fmemopen failed");
68        }
69
70        Ok(Self { file, buffer })
71    }
72
73    /// Get the raw FILE pointer wrapper.
74    /// This mutably borrows `self`, ensuring no other borrows (like `data()`)
75    /// can exist while this wrapper is alive.
76    pub fn file(&mut self) -> CFilePtr<'_> {
77        CFilePtr { ptr: self.file, _marker: PhantomData }
78    }
79
80    /// Flush the stream and return a slice over the data that has been written.
81    pub fn data(&self) -> &[u8] {
82        // Flush to ensure all data is written to the buffer.
83        // SAFETY: self.file is a valid FILE pointer created during construction.
84        unsafe {
85            libc::fflush(self.file);
86        }
87
88        // Find the current position in the file.
89        // SAFETY: self.file is a valid FILE pointer created during construction.
90        let pos = unsafe { libc::ftell(self.file) };
91        if pos < 0 {
92            return &[];
93        }
94
95        let len = pos as usize;
96        let len = std::cmp::min(len, self.buffer.len());
97
98        &self.buffer[..len]
99    }
100
101    /// Reset the position of the FILE* to the start of the file.
102    pub fn reset(&self) -> Result<(), &'static str> {
103        // SAFETY: self.file is a valid FILE pointer created during construction.
104        let ret = unsafe { libc::fseek(self.file, 0, libc::SEEK_SET) };
105        if ret != 0 {
106            return Err("fseek failed");
107        }
108        Ok(())
109    }
110}
111
112impl Drop for CFileBuffer {
113    fn drop(&mut self) {
114        // SAFETY: self.file is a valid FILE pointer that has not been closed yet.
115        unsafe {
116            libc::fclose(self.file);
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_write_and_read() {
127        let mut fmem = CFileBuffer::new(1024).unwrap();
128
129        // Write some data using libc
130        let data_to_write = b"hello world";
131        // SAFETY: The file pointer is valid, and the data buffer is valid for the duration of the write.
132        unsafe {
133            let file_ptr = fmem.file();
134            assert!(!file_ptr.as_raw().is_null());
135            libc::fwrite(
136                data_to_write.as_ptr() as *const libc::c_void,
137                1,
138                data_to_write.len(),
139                file_ptr.as_raw(),
140            );
141        }
142
143        // Read data back
144        let written = fmem.data();
145        assert_eq!(written, data_to_write);
146    }
147
148    #[test]
149    fn test_reset() {
150        let mut fmem = CFileBuffer::new(1024).unwrap();
151
152        let data1 = b"abc";
153        // SAFETY: The file pointer is valid, and the data buffer is valid for the duration of the write.
154        unsafe {
155            let file_ptr = fmem.file();
156            libc::fwrite(data1.as_ptr() as *const libc::c_void, 1, data1.len(), file_ptr.as_raw());
157        }
158        assert_eq!(fmem.data(), data1);
159
160        fmem.reset().unwrap();
161        assert_eq!(fmem.data(), b"");
162
163        let data2 = b"defgh";
164        // SAFETY: The file pointer is valid, and the data buffer is valid for the duration of the write.
165        unsafe {
166            let file_ptr = fmem.file();
167            libc::fwrite(data2.as_ptr() as *const libc::c_void, 1, data2.len(), file_ptr.as_raw());
168        }
169        assert_eq!(fmem.data(), data2);
170    }
171
172    #[test]
173    fn test_overflow() {
174        let mut fmem = CFileBuffer::new(5).unwrap();
175
176        let data = b"abcdefg";
177        // SAFETY: The file pointer is valid, and the data buffer is valid for the duration of the write.
178        unsafe {
179            let file_ptr = fmem.file();
180            libc::fwrite(data.as_ptr() as *const libc::c_void, 1, data.len(), file_ptr.as_raw());
181        }
182        // It should be capped at 5
183        assert_eq!(fmem.data(), b"abcde");
184    }
185
186    #[test]
187    fn test_exact_size() {
188        let mut fmem = CFileBuffer::new(5).unwrap();
189
190        let data = b"12345";
191        // SAFETY: The file pointer is valid, and the data buffer is valid for the duration of the write.
192        unsafe {
193            let file_ptr = fmem.file();
194            libc::fwrite(data.as_ptr() as *const libc::c_void, 1, data.len(), file_ptr.as_raw());
195        }
196        assert_eq!(fmem.data(), b"12345");
197    }
198}