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
141
142
143
144
145
146
147
148
149
150
//! An extension trait for safely dropping [I/O writers](std::io::Write)
//! such as [`File`](std::fs::File) and
//! [`BufWriter`](std::io::BufWriter). Specifically, it is for I/O
//! writers which contain a resource handle (such as a raw file
//! descriptor), and where the automatic process of closing this handle
//! during drop may generate an unseen I/O error. Using this trait
//! (called [`Close`]) these errors can be seen and dealt with.
//!
//! In the case of Linux [the man page for
//! close(2)](https://linux.die.net/man/2/close) has the following to
//! say:
//!
//! > Not checking the return value of close() is a common but
//! > nevertheless serious programming error. It is quite possible that
//! > errors on a previous write(2) operation are first reported at the
//! > final close(). Not checking the return value when closing the file
//! > may lead to silent loss of data. This can especially be observed
//! > with NFS and with disk quota.
//!
//! Provided implementations of [`Close`] are for standard library I/O
//! writers containing a raw file descriptor (Unix) or a raw handle or
//! raw socket (Windows).
//!
//! # BufWriter example
//!
//! ```
//! use std::io::{BufWriter, Result, Write};
//! use io_close::Close;
//!
//! fn main() -> Result<()> {
//!     let data = b"hello world";
//!     let mut buffer = BufWriter::new(tempfile::tempfile()?);
//!     buffer.write_all(data)?;
//!     buffer.close()?; // safely drop buffer and its contained File
//!     Ok(())
//! }
//! ```

use std::io::{BufWriter, Error, LineWriter, Result, Write};

pub mod fs;

/// An extension trait for safely dropping I/O writers containing a
/// resource handle.
pub trait Close: Write {
    /// Drops an I/O writer and closes its contained resource handle
    /// (such as a raw file descriptor), ensuring I/O errors resulting
    /// from closing this handle are not ignored. The writer is flushed
    /// before its handle is closed. If any errors occur during flushing
    /// or closing the first such error is returned.
    fn close(self) -> Result<()>;
}

macro_rules! unix_impl_close_raw_fd {
    ($ty:ty) => {
        #[cfg(unix)]
        impl Close for $ty {
            /// Drops an I/O writer containing a raw file descriptor.
            fn close(mut self) -> Result<()> {
                use std::io::ErrorKind;
                use std::os::unix::io::IntoRawFd;

                self.flush()?;
                let fd = self.into_raw_fd();
                let rv = unsafe { libc::close(fd) };
                if rv != -1 {
                    Ok(())
                } else {
                    match Error::last_os_error() {
                        e if e.kind() == ErrorKind::Interrupted => Ok(()),
                        e => Err(e),
                    }
                }
            }
        }
    };
}

macro_rules! windows_impl_close_raw_handle {
    ($ty:ty) => {
        #[cfg(windows)]
        impl Close for $ty {
            /// Drops an I/O writer containing a raw handle.
            fn close(mut self) -> Result<()> {
                use std::os::windows::io::IntoRawHandle;
                use winapi::um::handleapi;

                self.flush()?;
                let handle = self.into_raw_handle();
                let rv = unsafe { handleapi::CloseHandle(handle) };
                if rv != 0 {
                    Ok(())
                } else {
                    Err(Error::last_os_error())
                }
            }
        }
    };
}

macro_rules! windows_impl_close_raw_socket {
    ($ty:ty) => {
        #[cfg(windows)]
        impl Close for $ty {
            /// Drops an I/O writer containing a raw socket.
            fn close(mut self) -> Result<()> {
                use std::convert::TryInto;
                use std::os::windows::io::IntoRawSocket;
                use winapi::um::winsock2;

                self.flush()?;
                let socket = self.into_raw_socket().try_into().unwrap();
                let rv = unsafe { winsock2::closesocket(socket) };
                if rv == 0 {
                    Ok(())
                } else {
                    Err(Error::from_raw_os_error(unsafe {
                        winsock2::WSAGetLastError()
                    }))
                }
            }
        }
    };
}

unix_impl_close_raw_fd!(std::fs::File);
unix_impl_close_raw_fd!(std::net::TcpStream);
unix_impl_close_raw_fd!(std::os::unix::net::UnixStream);
unix_impl_close_raw_fd!(std::process::ChildStdin);

windows_impl_close_raw_handle!(std::fs::File);
windows_impl_close_raw_handle!(std::process::ChildStdin);

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

impl<W: Close> Close for BufWriter<W> {
    /// Drops a [`BufWriter`](std::io::BufWriter) containing an I/O
    /// writer implementing [`Close`].
    fn close(self) -> Result<()> {
        self.into_inner()?.close()
    }
}

impl<W: Close> Close for LineWriter<W> {
    /// Drops a [`LineWriter`](std::io::LineWriter) containing an I/O
    /// writer implementing [`Close`].
    fn close(self) -> Result<()> {
        self.into_inner()?.close()
    }
}