1#![cfg_attr(not(feature = "std"), no_std)]
11#![cfg_attr(do_doc_cfg, feature(doc_cfg))]
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15
16use core::{
17 ffi::CStr,
18 fmt,
19 marker::PhantomData,
20 mem,
21 ptr::{self, NonNull},
22};
23use libc::{c_char, c_int, c_void};
24
25#[cfg(feature = "std")]
26mod io;
27#[cfg_attr(do_doc_cfg, doc(cfg(feature = "std")))]
28#[cfg(feature = "std")]
29pub use io::Io;
30
31pub type RawCStream = NonNull<libc::FILE>;
33
34#[derive(Debug)]
42#[repr(transparent)]
43pub struct OwnedCStream {
44 raw: RawCStream,
45}
46
47impl Drop for OwnedCStream {
48 fn drop(&mut self) {
49 unsafe {
50 let _ = libc::fclose(self.raw.as_ptr());
52 }
53 }
54}
55
56impl AsCStream for OwnedCStream {
57 fn as_c_stream(&self) -> BorrowedCStream<'_> {
58 unsafe { BorrowedCStream::borrow_raw(self.raw) }
59 }
60}
61
62impl AsRawCStream for OwnedCStream {
63 fn as_raw_c_stream(&self) -> RawCStream {
64 self.raw
65 }
66}
67
68impl FromRawCStream for OwnedCStream {
69 unsafe fn from_raw_c_stream(raw: RawCStream) -> Self {
70 Self { raw }
71 }
72}
73
74impl IntoRawCStream for OwnedCStream {
75 fn into_raw_c_stream(self) -> RawCStream {
76 let raw = self.raw;
77 mem::forget(self);
78 raw
79 }
80}
81
82#[cfg(feature = "alloc")]
84#[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
85#[derive(Debug)]
86pub struct BufferedCStream {
87 stream: OwnedCStream,
88 #[allow(unused)]
90 buffer: alloc::boxed::Box<[u8]>,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
94pub enum BufferMode {
95 Block,
96 Line,
97}
98
99#[cfg(feature = "alloc")]
100impl BufferedCStream {
101 pub fn new(stream: OwnedCStream, size: usize, mode: BufferMode) -> Option<Self> {
102 let mut buffer = alloc::vec![0; size].into_boxed_slice();
103 match unsafe {
104 libc::setvbuf(
105 ptr(&stream),
106 buffer.as_mut_ptr().cast::<c_char>(),
107 match mode {
108 BufferMode::Block => libc::_IOFBF,
109 BufferMode::Line => libc::_IOLBF,
110 },
111 buffer.len(),
112 )
113 } {
114 0 => Some(Self { stream, buffer }),
115 _ => None,
116 }
117 }
118}
119
120#[cfg(feature = "alloc")]
121#[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
122impl AsCStream for BufferedCStream {
123 fn as_c_stream(&self) -> BorrowedCStream<'_> {
124 self.stream.as_c_stream()
125 }
126}
127
128#[cfg(feature = "alloc")]
129#[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
130impl AsRawCStream for BufferedCStream {
131 fn as_raw_c_stream(&self) -> RawCStream {
132 self.stream.as_raw_c_stream()
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
149#[repr(transparent)]
150pub struct BorrowedCStream<'a> {
151 raw: RawCStream,
152 lifetime: PhantomData<&'a OwnedCStream>,
153}
154
155impl<'a> BorrowedCStream<'a> {
156 pub const unsafe fn borrow_raw(raw: RawCStream) -> Self {
161 Self {
162 raw,
163 lifetime: PhantomData,
164 }
165 }
166}
167
168impl AsCStream for BorrowedCStream<'_> {
169 fn as_c_stream(&self) -> BorrowedCStream<'_> {
170 *self
171 }
172}
173
174impl AsRawCStream for BorrowedCStream<'_> {
175 fn as_raw_c_stream(&self) -> RawCStream {
176 self.raw
177 }
178}
179
180macro_rules! pointer_impls {
181 ($trait:ident, $method:ident, $return:ty) => {
182 impl<T: $trait + ?Sized> $trait for &T {
183 fn $method(&self) -> $return {
184 T::$method(self)
185 }
186 }
187 impl<T: $trait + ?Sized> $trait for &mut T {
188 fn $method(&self) -> $return {
189 T::$method(self)
190 }
191 }
192 #[cfg(feature = "alloc")]
193 #[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
194 impl<T: $trait + ?Sized> $trait for alloc::boxed::Box<T> {
195 fn $method(&self) -> $return {
196 T::$method(self)
197 }
198 }
199 #[cfg(feature = "alloc")]
200 #[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
201 impl<T: $trait + ?Sized> $trait for alloc::rc::Rc<T> {
202 fn $method(&self) -> $return {
203 T::$method(self)
204 }
205 }
206 #[cfg(feature = "alloc")]
207 #[cfg_attr(do_doc_cfg, doc(cfg(feature = "alloc")))]
208 impl<T: $trait + ?Sized> $trait for alloc::sync::Arc<T> {
209 fn $method(&self) -> $return {
210 T::$method(self)
211 }
212 }
213 };
214}
215
216pub trait AsCStream {
218 fn as_c_stream(&self) -> BorrowedCStream<'_>;
220}
221pointer_impls!(AsCStream, as_c_stream, BorrowedCStream<'_>);
222
223pub trait AsRawCStream {
225 fn as_raw_c_stream(&self) -> RawCStream;
235}
236pointer_impls!(AsRawCStream, as_raw_c_stream, RawCStream);
237
238pub trait FromRawCStream {
240 unsafe fn from_raw_c_stream(raw: RawCStream) -> Self;
249}
250impl FromRawCStream for RawCStream {
251 unsafe fn from_raw_c_stream(raw: RawCStream) -> Self {
252 raw
253 }
254}
255
256pub trait IntoRawCStream {
258 fn into_raw_c_stream(self) -> RawCStream;
264}
265
266impl IntoRawCStream for RawCStream {
267 fn into_raw_c_stream(self) -> RawCStream {
268 self
269 }
270}
271
272pub fn tmpfile() -> Option<OwnedCStream> {
277 let raw = NonNull::new(unsafe { libc::tmpfile() })?;
278 Some(unsafe { OwnedCStream::from_raw_c_stream(raw) })
279}
280
281fn ptr<T: AsCStream>(stream: &T) -> *mut libc::FILE {
287 stream.as_c_stream().as_raw_c_stream().as_ptr()
288}
289
290#[allow(clippy::result_unit_err)]
292pub fn flush<T: AsCStream>(stream: T) -> Result<(), ()> {
293 let ret = unsafe { libc::fflush(ptr(&stream)) };
294 match ret {
295 0 => Ok(()),
296 libc::EOF => Err(()),
297 _undocumented => Err(()),
298 }
299}
300
301pub fn open(filename: &CStr, mode: &CStr) -> Option<OwnedCStream> {
303 let raw = NonNull::new(unsafe { libc::fopen(filename.as_ptr(), mode.as_ptr()) })?;
304 Some(unsafe { OwnedCStream::from_raw_c_stream(raw) })
305}
306
307pub fn reopen(filename: Option<&CStr>, mode: &CStr, stream: OwnedCStream) -> Option<OwnedCStream> {
309 let raw = NonNull::new(unsafe {
310 libc::freopen(
311 filename.map(CStr::as_ptr).unwrap_or_else(ptr::null),
312 mode.as_ptr(),
313 stream.into_raw_c_stream().as_ptr(),
314 )
315 })?;
316 Some(unsafe { OwnedCStream::from_raw_c_stream(raw) })
317}
318
319pub fn fileno<T: AsCStream>(stream: T) -> Option<c_int> {
321 match unsafe { libc::fileno(ptr(&stream)) } {
322 -1 => None,
323 other => Some(other),
324 }
325}
326
327pub fn getc<T: AsCStream>(stream: T) -> Option<u8> {
333 match unsafe { libc::fgetc(ptr(&stream)) } {
334 libc::EOF => None,
335 it => Some(it as u8),
336 }
337}
338
339#[allow(clippy::result_unit_err)]
341pub fn ungetc<T: AsCStream>(char: u8, stream: T) -> Result<(), ()> {
342 match unsafe { libc::ungetc(char as c_int, ptr(&stream)) } {
343 libc::EOF => Err(()),
344 _ => Ok(()),
345 }
346}
347
348pub fn gets<T: AsCStream>(buf: &mut [u8], stream: T) -> Option<&CStr> {
350 match unsafe {
351 libc::fgets(
352 buf.as_mut_ptr().cast::<c_char>(),
353 buf.len().try_into().ok()?,
354 ptr(&stream),
355 )
356 }
357 .is_null()
358 {
359 true => None,
360 false => CStr::from_bytes_until_nul(buf).ok(),
361 }
362}
363
364#[allow(clippy::result_unit_err, clippy::if_same_then_else)]
366pub fn putc<T: AsCStream>(char: u8, stream: T) -> Result<(), ()> {
367 let ret = unsafe { libc::fputc(char as c_int, ptr(&stream)) };
368 if ret == libc::EOF {
369 Err(())
370 } else {
371 Ok(())
372 }
373}
374
375#[allow(clippy::result_unit_err)]
377pub fn puts<T: AsCStream>(s: &CStr, stream: T) -> Result<(), ()> {
378 let ret = unsafe { libc::fputs(s.as_ptr(), ptr(&stream)) };
379 if ret == libc::EOF {
380 Err(())
381 } else if ret > 0 {
382 Ok(())
383 } else {
384 Err(()) }
386}
387
388pub fn read<T: AsCStream>(buf: &mut [u8], stream: T) -> usize {
394 unsafe {
395 libc::fread(
396 buf.as_mut_ptr().cast::<c_void>(),
397 1,
398 buf.len(),
399 ptr(&stream),
400 )
401 }
402}
403
404pub fn write<T: AsCStream>(buf: &[u8], stream: T) -> usize {
406 unsafe { libc::fwrite(buf.as_ptr().cast::<c_void>(), 1, buf.len(), ptr(&stream)) }
407}
408#[cfg(feature = "std")]
414#[cfg_attr(do_doc_cfg, doc(cfg(feature = "std")))]
415#[allow(clippy::result_unit_err)]
416pub fn seek<T: AsCStream>(stream: T, pos: std::io::SeekFrom) -> Result<(), ()> {
417 let (offset, whence) = match pos {
418 std::io::SeekFrom::Start(it) => (it.try_into().map_err(|_| ())?, libc::SEEK_SET),
419 std::io::SeekFrom::End(it) => (it, libc::SEEK_END),
420 std::io::SeekFrom::Current(it) => (it, libc::SEEK_CUR),
421 };
422 match unsafe { libc::fseek(ptr(&stream), offset, whence) } {
423 0 => Ok(()),
424 -1 => Err(()),
425 _undocumented => Err(()),
426 }
427}
428
429pub fn tell<T: AsCStream>(stream: T) -> Option<u64> {
431 unsafe { libc::ftell(ptr(&stream)) }.try_into().ok()
432}
433
434pub fn rewind<T: AsCStream>(stream: T) {
436 unsafe { libc::rewind(ptr(&stream)) }
437}
438
439pub fn eof<T: AsCStream>(stream: T) -> bool {
445 unsafe { libc::feof(ptr(&stream)) != 0 }
446}
447
448pub fn clear_errors<T: AsCStream>(stream: T) {
450 unsafe { libc::clearerr(ptr(&stream)) }
451}
452
453#[derive(Debug, Clone)]
455pub struct FError(pub i32);
456
457impl FError {
458 pub fn of<T: AsCStream>(stream: T) -> Self {
459 Self(unsafe { libc::ferror(ptr(&stream)) })
460 }
461}
462
463impl fmt::Display for FError {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 f.write_fmt(format_args!("error indicator {} was set on stream", self.0))
466 }
467}
468
469#[cfg(feature = "std")]
470impl std::error::Error for FError {}
471
472#[cfg(all(test, feature = "std"))]
473fn read_to_string(mut f: impl std::io::Read) -> String {
474 let mut s = String::new();
475 f.read_to_string(&mut s).unwrap();
476 s
477}
478
479#[cfg(all(test, feature = "std"))]
480mod tests {
481 use super::*;
482 use std::{ffi::CString, fs, os::unix::ffi::OsStrExt as _};
483 use tempfile::NamedTempFile;
484
485 #[test]
486 fn test() {
487 let named = NamedTempFile::new().unwrap();
488 let path = CString::new(named.path().as_os_str().as_bytes()).unwrap();
489 let stream = open(&path, c"rw+").unwrap();
490 assert_eq!(write(b"hello, world!", stream), 13);
491 assert_eq!(fs::read_to_string(named.path()).unwrap(), "hello, world!");
492 }
493}