libnotcurses_sys/
file.rs

1//! Wrapper for `libc::FILE`, both as used by notcurses and the libc crate
2//!
3//! The interface is largely based on the implementation of the
4//! [cfile-rs crate](https://github.com/jkarns275/cfile) by Joshua Karns
5
6use core::ptr::{null_mut, NonNull};
7use std::io::{Error, ErrorKind, Read, Seek, SeekFrom};
8
9use libc::{
10    c_long, c_void, fclose, /* feof, */ fread, fseek, ftell, SEEK_CUR, SEEK_END, SEEK_SET,
11};
12
13// A utility function to pull the current value of errno and put it into an
14// Error::Errno
15fn get_error<T>() -> Result<T, Error> {
16    Err(Error::last_os_error())
17}
18
19// See [`NcFile`]. Notcurses functions expects this type of `*FILE` (a struct)
20#[allow(clippy::upper_case_acronyms)]
21type NcFile_nc = crate::c_api::ffi::FILE;
22
23// See [`NcFile`]. The [`libc`](https://docs.rs/libc/) crate expects this type
24// of `*FILE` (an opaque enum)
25#[allow(clippy::upper_case_acronyms)]
26type NcFile_libc = libc::FILE;
27
28/// A wrapper struct around
29/// [`libc::FILE`](https://docs.rs/libc/0.2.80/libc/enum.FILE.html)
30///
31/// The `notcurses`' [`FILE`][crate::c_api::ffi::FILE] type is a transparent
32/// struct, while the equivalent `libc`'s [`FILE`][libc::FILE] is an opaque enum.
33///
34/// Several methods are provided to cast back and forth between both types,
35/// in order to allow both rust libc operations and notcurses file operations
36/// over the same underlying `*FILE`.
37#[derive(Debug)]
38pub struct NcFile {
39    file_ptr: NonNull<NcFile_libc>,
40}
41
42/// # Constants
43impl NcFile {
44    // TODO: the following static strings aren't made public
45
46    /// Intended to be passed into the CFile::open method.
47    /// It will open the file in a way that will allow reading and writing,
48    /// including overwriting old data.
49    /// It will not create the file if it does not exist.
50    pub const RANDOM_ACCESS_MODE: &'static str = "rb+";
51
52    /// Intended to be passed into the CFile::open method.
53    /// It will open the file in a way that will allow reading and writing,
54    /// including overwriting old data
55    pub const UPDATE: &'static str = "rb+";
56
57    /// Intended to be passed into the CFile::open method.
58    /// It will only allow reading.
59    pub const READ_ONLY: &'static str = "r";
60
61    /// Intended to be passed into the CFile::open method.
62    /// It will only allow writing.
63    pub const WRITE_ONLY: &'static str = "w";
64
65    /// Intended to be passed into the CFile::open method.
66    /// It will only allow data to be appended to the end of the file.
67    pub const APPEND_ONLY: &'static str = "a";
68
69    /// Intended to be passed into the CFile::open method.
70    /// It will allow data to be appended to the end of the file, and data to be
71    /// read from the file. It will create the file if it doesn't exist.
72    pub const APPEND_READ: &'static str = "a+";
73
74    /// Intended to be passed into the CFile::open method.
75    /// It will open the file in a way that will allow reading and writing,
76    /// including overwriting old data. It will create the file if it doesn't exist
77    pub const TRUNCATE_RANDOM_ACCESS_MODE: &'static str = "wb+";
78}
79
80/// # Constructors
81impl NcFile {
82    /// `NcFile` constructor from a file produced by notcurses.
83    pub unsafe fn from_nc(file: *mut NcFile_nc) -> Self {
84        NcFile { file_ptr: unsafe { NonNull::new_unchecked(NcFile::nc2libc(file)) } }
85    }
86
87    /// `NcFile` constructor from a file produced by the libc crate.
88    #[allow(clippy::missing_safety_doc)]
89    pub unsafe fn from_libc(file: *mut NcFile_libc) -> Self {
90        NcFile { file_ptr: NonNull::new_unchecked(file) }
91    }
92}
93
94/// # Methods
95impl NcFile {
96    /// Returns the file pointer in the format expected by the [`libc`] crate.
97    #[inline]
98    pub fn as_libc_ptr(&self) -> *mut NcFile_libc {
99        self.file_ptr.as_ptr()
100    }
101
102    /// Returns the file pointer in the format expected by notcurses.
103    #[inline]
104    pub fn as_nc_ptr(&self) -> *mut NcFile_nc {
105        Self::libc2nc(self.file_ptr.as_ptr())
106    }
107
108    /// Returns the current position in the file.
109    ///
110    /// On error `Error::Errno(errno)` is returned.
111    pub fn current_pos(&self) -> Result<u64, Error> {
112        unsafe {
113            let pos = ftell(self.as_libc_ptr());
114            if pos != -1 {
115                Ok(pos as u64)
116            } else {
117                get_error()
118            }
119        }
120    }
121
122    /// Reads the file from start to end. Convenience method.
123    ///
124    #[inline]
125    pub fn read_all(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error> {
126        let _ = self.seek(SeekFrom::Start(0));
127        self.read_to_end(buf)
128    }
129
130    // private methods:
131
132    /// Converts a file pointer from the struct notcurses uses to the
133    /// opaque enum type libc expects.
134    #[inline]
135    fn nc2libc(file: *mut NcFile_nc) -> *mut NcFile_libc {
136        file as *mut _ as *mut NcFile_libc
137    }
138
139    /// Converts a file pointer from the libc opaque enum format to the struct
140    /// expected by notcurses.
141    #[inline]
142    fn libc2nc(file: *mut NcFile_libc) -> *mut NcFile_nc {
143        file as *mut _ as *mut NcFile_nc
144    }
145
146    /// A utility function to expand a vector without increasing its capacity
147    /// more than it needs to be expanded.
148    fn expand_buffer(buff: &mut Vec<u8>, by: usize) {
149        if buff.capacity() < buff.len() + by {
150            buff.reserve(by);
151        }
152        for _ in 0..by {
153            buff.push(0u8);
154        }
155    }
156}
157
158impl Read for NcFile {
159    /// Reads exactly the number of bytes required to fill buf.
160    ///
161    /// If the end of the file is reached before buf is filled,
162    /// `Err(EndOfFile(bytes_read))` will be returned.  The data that was read
163    /// before that will still have been placed into buf.
164    ///
165    /// Upon some other error, `Err(Errno(errno))` will be returned.
166    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
167        unsafe {
168            let result = fread(
169                buf.as_ptr() as *mut c_void,
170                1,
171                buf.len(),
172                self.as_libc_ptr(),
173            );
174            if result != buf.len() {
175                match get_error::<u8>() {
176                    Err(err) => {
177                        if err.kind() == ErrorKind::UnexpectedEof {
178                            Ok(result)
179                        } else {
180                            Err(err)
181                        }
182                    }
183                    Ok(_) => panic!("This is impossible"),
184                }
185            } else {
186                Ok(result)
187            }
188        }
189    }
190
191    /// Reads the entire file starting from the current_position
192    /// expanding buf as needed.
193    ///
194    /// On a successful read, this function will return `Ok(bytes_read)`.
195    ///
196    /// If an error occurs during reading, some varient of error will be returned.
197    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Error> {
198        let pos = self.current_pos();
199        let _ = self.seek(SeekFrom::End(0));
200        let end = self.current_pos();
201        match pos {
202            Ok(cur_pos) => match end {
203                Ok(end_pos) => {
204                    if end_pos == cur_pos {
205                        return Ok(0);
206                    }
207                    let to_read = (end_pos - cur_pos) as usize;
208                    if buf.len() < to_read {
209                        let to_reserve = to_read - buf.len();
210                        Self::expand_buffer(buf, to_reserve);
211                    }
212                    let _ = self.seek(SeekFrom::Start(cur_pos));
213                    match self.read_exact(buf) {
214                        Ok(()) => Ok(to_read),
215                        Err(e) => Err(e),
216                    }
217                }
218                Err(e) => Err(e),
219            },
220            Err(e) => Err(e),
221        }
222    }
223
224    /// Reads the entire file from the beginning and stores it in a string.
225    ///
226    /// On a successful read, this function will return `Ok(bytes_read)`.
227    ///
228    /// If an error occurs during reading, some varient of error will be returned.
229    fn read_to_string(&mut self, strbuf: &mut String) -> Result<usize, Error> {
230        let mut buffer = vec![0u8];
231
232        let bytes_read = self.read_all(&mut buffer)?;
233
234        let result = std::str::from_utf8(&buffer);
235
236        if let Ok(strslice) = result {
237            *strbuf = strslice.to_string();
238            Ok(bytes_read)
239        } else {
240            get_error()
241        }
242    }
243
244    /// Reads exactly the number of bytes required to fill buf.
245    ///
246    /// If the end of the file is reached before buf is filled,
247    /// `Err(EndOfFile(bytes_read))` will be returned. The data that was read
248    /// before that will still have been placed into buf.
249    ///
250    /// Upon some other error, `Err(Errno(errno))` will be returned.
251    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Error> {
252        unsafe {
253            let result = fread(
254                buf.as_ptr() as *mut c_void,
255                1,
256                buf.len(),
257                self.as_libc_ptr(),
258            );
259            if result == buf.len() {
260                Ok(())
261            } else {
262                // Check if we hit the end of the file
263                // FIXME
264                // if feof(self.as_libc_ptr()) != 0 {
265                //     get_error()
266                // } else {
267                //     get_error()
268                // }
269                get_error()
270            }
271        }
272    }
273}
274
275impl Seek for NcFile {
276    /// Changes the current position in the file using the [`SeekFrom`] enum.
277    ///
278    /// To set relative to the beginning of the file (i.e. index is 0 + offset):
279    /// ```ignore
280    /// SeekFrom::Start(offset)
281    /// ```
282    /// To set relative to the end of the file (i.e. index is file_lenth - 1 - offset):
283    /// ```ignore
284    /// SeekFrom::End(offset)
285    /// ```
286    /// To set relative to the current position:
287    /// ```ignore
288    /// SeekFrom::End(offset)
289    /// ```
290    ///
291    /// On error `Error::Errno(errno)` is returned.
292    fn seek(&mut self, pos: SeekFrom) -> Result<u64, Error> {
293        unsafe {
294            let result = match pos {
295                SeekFrom::Start(from) => fseek(self.as_libc_ptr(), from as c_long, SEEK_SET),
296                SeekFrom::End(from) => fseek(self.as_libc_ptr(), from as c_long, SEEK_END),
297                SeekFrom::Current(delta) => fseek(self.as_libc_ptr(), delta as c_long, SEEK_CUR),
298            };
299            if result == 0 {
300                self.current_pos()
301            } else {
302                get_error()
303            }
304        }
305    }
306}
307
308impl Drop for NcFile {
309    /// Ensures the file stream is closed before abandoning the data.
310    fn drop(&mut self) {
311        let _ = unsafe {
312            if !(self.as_libc_ptr()).is_null() {
313                let res = fclose(self.as_libc_ptr());
314                if res == 0 {
315                    self.file_ptr = NonNull::new_unchecked(null_mut::<NcFile_libc>());
316                    Ok(())
317                } else {
318                    get_error()
319                }
320            } else {
321                Ok(())
322            }
323        };
324    }
325}