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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
use crate::{Clircle, Stdio};
use std::convert::TryFrom;
use std::fs::File;
use std::io::{self, Seek, SeekFrom};
use std::os::unix::fs::MetadataExt;
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::{cmp, hash, ops};
pub use libc;
#[derive(Debug)]
pub struct UnixIdentifier {
device: u64,
inode: u64,
size: u64,
is_regular_file: bool,
file: Option<File>,
owns_fd: bool,
}
impl UnixIdentifier {
fn file(&self) -> &File {
self.file.as_ref().expect("Called file() on an identifier that has already been destroyed, this should never happen! Please file a bug!")
}
fn current_file_offset(&self) -> io::Result<u64> {
self.file().seek(SeekFrom::Current(0))
}
fn has_content_left_to_read(&self) -> io::Result<bool> {
Ok(self.current_file_offset()? < self.size)
}
pub unsafe fn try_from_raw_fd(fd: RawFd, owns_fd: bool) -> io::Result<Self> {
Self::try_from(File::from_raw_fd(fd)).map(|mut ident| {
ident.owns_fd = owns_fd;
ident
})
}
}
impl Clircle for UnixIdentifier {
#[must_use]
fn into_inner(mut self) -> Option<File> {
if self.owns_fd {
self.owns_fd = false;
self.file.take()
} else {
None
}
}
#[must_use]
fn surely_conflicts_with(&self, other: &Self) -> bool {
PartialEq::eq(self, other)
&& self.is_regular_file
&& other.has_content_left_to_read().unwrap_or(true)
}
}
impl TryFrom<Stdio> for UnixIdentifier {
type Error = <Self as TryFrom<File>>::Error;
fn try_from(stdio: Stdio) -> Result<Self, Self::Error> {
let fd = match stdio {
Stdio::Stdin => libc::STDIN_FILENO,
Stdio::Stdout => libc::STDOUT_FILENO,
Stdio::Stderr => libc::STDERR_FILENO,
};
unsafe { Self::try_from_raw_fd(fd, false) }
}
}
impl ops::Drop for UnixIdentifier {
fn drop(&mut self) {
if !self.owns_fd {
let _ = self.file.take().map(IntoRawFd::into_raw_fd);
}
}
}
impl TryFrom<File> for UnixIdentifier {
type Error = io::Error;
fn try_from(file: File) -> Result<Self, Self::Error> {
file.metadata().map(|metadata| Self {
device: metadata.dev(),
inode: metadata.ino(),
size: metadata.size(),
is_regular_file: metadata.file_type().is_file(),
file: Some(file),
owns_fd: true,
})
}
}
impl cmp::PartialEq for UnixIdentifier {
#[must_use]
fn eq(&self, other: &Self) -> bool {
self.device == other.device && self.inode == other.inode
}
}
impl Eq for UnixIdentifier {}
impl hash::Hash for UnixIdentifier {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.device.hash(state);
self.inode.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use std::io::Write;
use nix::pty::{openpty, OpenptyResult};
use nix::unistd::close;
#[test]
fn test_fd_closing() -> Result<(), Box<dyn Error>> {
let dir = tempfile::tempdir().expect("Couldn't create tempdir.");
let dir_path = dir.path().to_path_buf();
let file = File::create(dir_path.join("myfile"))?;
let ident = UnixIdentifier::try_from(file)?;
let mut file = ident
.into_inner()
.ok_or("Did not get file back from identifier")?;
file.write_all(b"Some test content")?;
let fd = file.into_raw_fd();
let ident = unsafe { UnixIdentifier::try_from_raw_fd(fd, false) };
if let Err(e) = ident {
let _ = dbg!(close(fd));
return Err(Box::new(e));
}
let ident = ident.unwrap();
drop(ident);
close(fd).map_err(|e| {
format!(
"Error closing file, that I told UnixIdentifier not to close: {}",
e
)
})?;
let fd = File::open(dir_path.join("myfile"))?.into_raw_fd();
let ident = unsafe { UnixIdentifier::try_from_raw_fd(fd, true) };
if let Err(e) = ident {
let _ = dbg!(close(fd));
return Err(Box::new(e));
}
let ident = ident.unwrap();
drop(ident);
close(fd).expect_err("This file descriptor should have been closed already!");
Ok(())
}
#[test]
fn test_pty_equal_but_not_conflicting() -> Result<(), &'static str> {
let OpenptyResult { master, slave } = openpty(None, None).expect("Could not open pty.");
let res = unsafe { UnixIdentifier::try_from_raw_fd(slave, false) }
.map_err(|_| "Error creating UnixIdentifier from pty fd")
.and_then(|ident| {
if !ident.eq(&ident) {
return Err("ident != ident");
}
if ident.surely_conflicts_with(&ident) {
return Err("pty fd does not conflict with itself, but conflict detected");
}
let second_ident = unsafe { UnixIdentifier::try_from_raw_fd(slave, false) }
.map_err(|_| "Error creating second Identifier to pty")?;
if !ident.eq(&second_ident) {
return Err("ident != second_ident");
}
if ident.surely_conflicts_with(&second_ident) {
return Err(
"Two Identifiers to the same pty should not conflict, but they do.",
);
}
Ok(())
});
let r1 = close(master);
let r2 = close(slave);
r1.expect("Error closing master end of pty");
r2.expect("Error closing slave end of pty");
res
}
}