1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! An extension trait ([`Close`]) for closing I/O related types such as
//! [`File`](std::fs::File) and [`BufWriter`](std::io::BufWriter).
//! Specifically it is for types containing a resource handle which,
//! when closed, may return an error.
//!
//! Provided implementations are for standard library types which
//! implement (or contain types which implement):
//!
//! - [`IntoRawFd`](https://doc.rust-lang.org/nightly/std/os/unix/io/trait.IntoRawFd.html) (Unix)
//!
//! - [`IntoRawHandle`](https://doc.rust-lang.org/nightly/std/os/windows/io/trait.IntoRawHandle.html) or
//!   [`IntoRawSocket`](https://doc.rust-lang.org/nightly/std/os/windows/io/trait.IntoRawSocket.html) (Windows).
//!
//! Using [`Close`] enables the handling of I/O errors which might
//! otherwise be ignored during drop.
//!
//! # BufWriter Example
//!
//! ```
//! use std::io::{BufWriter, Result, Write};
//! use io_close::Close;
//!
//! fn main() -> Result<()> {
//!     let data = "hello world".as_bytes();
//!     let mut buffer = BufWriter::new(tempfile::tempfile()?);
//!     buffer.write_all(data)?;
//!     buffer.close()?;
//!     Ok(())
//! }
//! ```

use std::io::{BufReader, BufWriter, Error, Read, Result, Write};
#[cfg(unix)]
use std::os::unix::io::IntoRawFd;
#[cfg(windows)]
use std::os::windows::{
    io::{IntoRawHandle, IntoRawSocket},
    raw::SOCKET,
};
#[cfg(windows)]
extern "system" {
    fn closesocket(socket: SOCKET) -> libc::c_int;
    fn WSAGetLastError() -> libc::c_int;
}

/// An extension trait for closing I/O related types containing a
/// resource handle which, when closed, may return an error.
pub trait Close {
    /// Consumes and closes an I/O related type and its contained
    /// resource handle (such as a file descriptor). If any I/O errors
    /// occur, the first such error is returned.
    fn close(self) -> Result<()>;
}

macro_rules! unix_impl_close_raw_fd {
    ($ty:ty $(,$ty_param:ident)*) => {
        #[cfg(unix)]
        /// Consumes and closes a type containing a raw file descriptor.
        impl<$($ty_param),*> Close for $ty {
            fn close(self) -> Result<()> {
                let fd = self.into_raw_fd();
                let ret = unsafe { libc::close(fd) };
                if ret == 0 { Ok(()) } else { Err(Error::last_os_error()) }
            }
        }
    };
}

macro_rules! windows_impl_close_raw_handle {
    ($ty:ty $(,$ty_param:ident)*) => {
        #[cfg(windows)]
        /// Consumes and closes a type containing a raw handle.
        impl<$($ty_param),*> Close for $ty {
            fn close(self) -> Result<()> {
                let handle = self.into_raw_handle();
                let ret = unsafe { kernel32::CloseHandle(handle) };
                if ret != 0 { Ok(()) } else { Err(Error::last_os_error()) }
            }
        }
    };
}

macro_rules! windows_impl_close_raw_socket {
    ($ty:ty $(,$ty_param:ident)*) => {
        #[cfg(windows)]
        impl<$($ty_param),*> Close for $ty {
            /// Consumes and closes a type containing a raw socket.
            fn close(self) -> Result<()> {
                let socket = self.into_raw_socket();
                let ret = unsafe { closesocket(socket) };
                if ret == 0 {
                    Ok(())
                } else {
                    Err(Error::from_raw_os_error(unsafe { WSAGetLastError() }))
                }
            }
        }
    };
}

unix_impl_close_raw_fd!(std::fs::File);
unix_impl_close_raw_fd!(std::net::TcpListener);
unix_impl_close_raw_fd!(std::net::TcpStream);
unix_impl_close_raw_fd!(std::net::UdpSocket);
unix_impl_close_raw_fd!(std::os::unix::io::RawFd);
unix_impl_close_raw_fd!(std::os::unix::net::UnixDatagram);
unix_impl_close_raw_fd!(std::os::unix::net::UnixListener);
unix_impl_close_raw_fd!(std::os::unix::net::UnixStream);
unix_impl_close_raw_fd!(std::process::ChildStderr);
unix_impl_close_raw_fd!(std::process::ChildStdin);
unix_impl_close_raw_fd!(std::process::ChildStdout);

windows_impl_close_raw_handle!(std::fs::File);
windows_impl_close_raw_handle!(std::process::Child);
windows_impl_close_raw_handle!(std::process::ChildStderr);
windows_impl_close_raw_handle!(std::process::ChildStdin);
windows_impl_close_raw_handle!(std::process::ChildStdout);
windows_impl_close_raw_handle!(std::thread::JoinHandle<T>, T);

windows_impl_close_raw_socket!(std::net::TcpListener);
windows_impl_close_raw_socket!(std::net::TcpStream);
windows_impl_close_raw_socket!(std::net::UdpSocket);

impl<R: Read + Close> Close for BufReader<R> {
    /// Consumes and closes a [`BufReader`](std::io::BufReader), and its
    /// contained [`Read`](std::io::Read) instance.
    fn close(self) -> Result<()> {
        self.into_inner().close()
    }
}

impl<W: Write + Close> Close for BufWriter<W> {
    /// Consumes and closes a [`BufWriter`](std::io::BufWriter) and its
    /// contained [`Write`](std::io::Write) instance. The
    /// [`BufWriter`](std::io::BufWriter) is flushed before closing. If
    /// any I/O errors occur, the first such error is returned.
    fn close(self) -> Result<()> {
        self.into_inner()?.close()
    }
}