cfile/
stream.rs

1use std::convert::AsRef;
2use std::fs::Metadata;
3use std::io;
4use std::mem;
5use std::path::PathBuf;
6use std::str;
7
8cfg_if! {
9    if #[cfg(unix)] {
10        use std::os::unix::io::{AsRawFd, IntoRawFd};
11    } else {
12        use std::ffi::OsString;
13        use std::ptr::NonNull;
14        use std::os::windows::ffi::OsStringExt;
15        use std::os::windows::io::{AsRawHandle, IntoRawHandle};
16    }
17}
18
19use foreign_types::ForeignTypeRef;
20
21use crate::{CFile, CFileRef};
22
23/// The C *FILE stream
24pub trait Stream: io::Read + io::Write + io::Seek {
25    /// returns the current position of the stream.
26    fn position(&self) -> io::Result<u64>;
27
28    /// tests the end-of-file indicator for the stream
29    fn eof(&self) -> bool;
30
31    /// tests the error indicator for the stream
32    fn errno(&self) -> i32;
33
34    /// get the last error of the stream
35    fn last_error(&self) -> Option<io::Error>;
36
37    /// clears the end-of-file and error indicators for the stream
38    fn clear_error(&self);
39
40    /// returns the file name of the stream
41    fn file_name(&self) -> io::Result<PathBuf>;
42
43    /// Queries metadata about the underlying file.
44    fn metadata(&self) -> io::Result<Metadata>;
45
46    /// Reads n elements of data, return the number of items read.
47    fn read_slice<T: Sized>(&mut self, elements: &mut [T]) -> io::Result<usize>;
48
49    /// Writes n elements of data, return the number of items written.
50    fn write_slice<T: Sized>(&mut self, elements: &[T]) -> io::Result<usize>;
51}
52
53cfg_if! {
54    if #[cfg(unix)] {
55        /// A trait for converting a raw fd to a C *FILE stream.
56        pub trait AsStream: AsRawFd + Sized {
57            /// Open a raw fd as C *FILE stream
58            fn as_stream<S: AsRef<str>>(&self, mode: S) -> io::Result<CFile> {
59                CFile::fdopen(self.as_raw_fd(), mode)
60            }
61        }
62
63        impl<S: AsRawFd + Sized> AsStream for S {}
64
65        /// A trait to express the ability to consume an object and acquire ownership of its stream.
66        pub trait IntoStream: IntoRawFd + Sized {
67            /// Consumes this raw fd, returning the raw underlying C *FILE stream.
68            fn into_stream<S: AsRef<str>>(self, mode: S) -> io::Result<CFile> {
69                CFile::fdopen(self.into_raw_fd(), mode)
70            }
71        }
72
73        impl<S: IntoRawFd + Sized> IntoStream for S {}
74    } else {
75        /// A trait for converting a raw fd to a C *FILE stream.
76        pub trait AsStream: AsRawHandle + Sized {
77            /// Open a raw fd as C *FILE stream
78            fn as_stream<S: AsRef<str>>(&self, mode: S) -> io::Result<CFile> {
79                CFile::fdopen(self.as_raw_handle(), mode)
80            }
81        }
82
83        impl<S: AsRawHandle + Sized> AsStream for S {}
84
85        /// A trait to express the ability to consume an object and acquire ownership of its stream.
86        pub trait IntoStream: IntoRawHandle + Sized {
87            /// Consumes this raw fd, returning the raw underlying C *FILE stream.
88            fn into_stream<S: AsRef<str>>(self, mode: S) -> io::Result<CFile> {
89                CFile::fdopen(self.into_raw_handle(), mode)
90            }
91        }
92
93        impl<S: IntoRawHandle + Sized> IntoStream for S {}
94    }
95}
96
97impl io::Read for CFile {
98    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
99        self.as_mut().read_slice(buf)
100    }
101}
102
103impl io::Write for CFile {
104    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
105        self.as_mut().write(buf)
106    }
107
108    fn flush(&mut self) -> io::Result<()> {
109        self.as_mut().flush()
110    }
111}
112
113impl io::Seek for CFile {
114    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
115        self.as_mut().seek(pos)
116    }
117}
118
119impl io::Read for CFileRef {
120    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
121        self.read_slice(buf)
122    }
123}
124
125impl io::Write for CFileRef {
126    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
127        self.write_slice(buf)
128    }
129
130    fn flush(&mut self) -> io::Result<()> {
131        if unsafe { libc::fflush(self.as_ptr()) } != 0 {
132            if let Some(err) = self.last_error() {
133                return Err(err);
134            }
135        }
136
137        Ok(())
138    }
139}
140
141cfg_if! {
142    if #[cfg(unix)] {
143        use libc::fseek as fseek64;
144    } else {
145        extern "C" {
146            #[link_name = "_fseeki64"]
147            fn fseek64(file: *mut libc::FILE, offset: i64, origin: i32) -> i32;
148        }
149    }
150}
151
152impl io::Seek for CFileRef {
153    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
154        let ret = unsafe {
155            match pos {
156                io::SeekFrom::Start(off) => fseek64(self.as_ptr(), off as i64, libc::SEEK_SET),
157                io::SeekFrom::End(off) => fseek64(self.as_ptr(), off, libc::SEEK_END),
158                io::SeekFrom::Current(off) => fseek64(self.as_ptr(), off, libc::SEEK_CUR),
159            }
160        };
161
162        if ret != 0 {
163            if let Some(err) = self.last_error() {
164                return Err(err);
165            }
166        }
167
168        self.position()
169    }
170}
171
172impl Stream for CFileRef {
173    fn position(&self) -> io::Result<u64> {
174        let off = unsafe { libc::ftell(self.as_ptr()) };
175
176        if off < 0 {
177            if let Some(err) = self.last_error() {
178                return Err(err);
179            }
180        }
181
182        Ok(off as u64)
183    }
184
185    #[inline]
186    fn eof(&self) -> bool {
187        unsafe { libc::feof(self.as_ptr()) != 0 }
188    }
189
190    #[inline]
191    fn errno(&self) -> i32 {
192        unsafe { libc::ferror(self.as_ptr()) }
193    }
194
195    fn last_error(&self) -> Option<io::Error> {
196        let errno = self.errno();
197
198        if errno != 0 {
199            return Some(io::Error::from_raw_os_error(errno));
200        }
201
202        let err = io::Error::last_os_error();
203
204        match err.raw_os_error() {
205            Some(errno) if errno != 0 => Some(err),
206            _ => None,
207        }
208    }
209
210    fn clear_error(&self) {
211        unsafe { clearerr(self.as_ptr()) }
212    }
213
214    #[cfg(target_os = "linux")]
215    fn file_name(&self) -> io::Result<PathBuf> {
216        use std::path::Path;
217
218        let s = format!("/proc/self/fd/{}", self.as_raw_fd());
219        let p = Path::new(&s);
220
221        if p.exists() {
222            p.read_link()
223        } else {
224            Err(io::Error::new(io::ErrorKind::NotFound, "fd not found"))
225        }
226    }
227
228    #[cfg(target_os = "macos")]
229    fn file_name(&self) -> io::Result<PathBuf> {
230        use std::ffi::CStr;
231
232        let mut buf = Vec::with_capacity(libc::PATH_MAX as usize);
233
234        let ret = unsafe { libc::fcntl(self.as_raw_fd(), libc::F_GETPATH, buf.as_mut_ptr()) };
235
236        let filename = str::from_utf8(unsafe { CStr::from_ptr(buf.as_ptr()).to_bytes() }).unwrap();
237
238        println!("{}, {}", filename, ret);
239
240        if ret < 0 {
241            Err(io::Error::last_os_error())
242        } else {
243            Ok(PathBuf::from(filename))
244        }
245    }
246
247    #[cfg(target_os = "windows")]
248    fn file_name(&self) -> io::Result<PathBuf> {
249        use winapi::shared::minwindef::MAX_PATH;
250        use winapi::um::fileapi::FILE_NAME_INFO;
251        use winapi::um::minwinbase::FileNameInfo;
252        use winapi::um::winbase::GetFileInformationByHandleEx;
253        use winapi::um::winnt::WCHAR;
254
255        let wchar_size = mem::size_of::<WCHAR>();
256        let bufsize = mem::size_of::<FILE_NAME_INFO>() + MAX_PATH * wchar_size;
257        let mut buf = vec![0u8; bufsize];
258
259        unsafe {
260            if GetFileInformationByHandleEx(
261                self.as_raw_handle() as *mut _,
262                FileNameInfo,
263                buf.as_mut_ptr() as *mut _,
264                buf.len() as u32,
265            ) == 0
266            {
267                Err(io::Error::last_os_error())
268            } else {
269                let fi = NonNull::new_unchecked(buf.as_mut_ptr()).cast::<FILE_NAME_INFO>();
270                let fi = fi.as_ref();
271                let filename = std::slice::from_raw_parts(
272                    fi.FileName.as_ptr(),
273                    fi.FileNameLength as usize / wchar_size,
274                );
275
276                Ok(PathBuf::from(OsString::from_wide(filename)))
277            }
278        }
279    }
280
281    fn metadata(&self) -> io::Result<Metadata> {
282        self.file_name()?.as_path().metadata()
283    }
284
285    fn read_slice<T: Sized>(&mut self, elements: &mut [T]) -> io::Result<usize> {
286        if elements.is_empty() {
287            return Ok(0);
288        }
289
290        let read = unsafe {
291            libc::fread(
292                elements.as_mut_ptr() as *mut libc::c_void,
293                mem::size_of::<T>(),
294                elements.len(),
295                self.as_ptr(),
296            )
297        };
298
299        if let Some(err) = self.last_error() {
300            if read == 0 {
301                return Err(err);
302            }
303        }
304
305        Ok(read)
306    }
307
308    fn write_slice<T: Sized>(&mut self, elements: &[T]) -> io::Result<usize> {
309        if elements.is_empty() {
310            return Ok(0);
311        }
312
313        let wrote = unsafe {
314            libc::fwrite(
315                elements.as_ptr() as *const libc::c_void,
316                mem::size_of::<T>(),
317                elements.len(),
318                self.as_ptr(),
319            )
320        };
321
322        if let Some(err) = self.last_error() {
323            if wrote == 0 {
324                return Err(err);
325            }
326        }
327
328        Ok(wrote)
329    }
330}
331
332extern "C" {
333    fn clearerr(file: *mut libc::FILE);
334}