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
use io_uring::types::OpenHow;
use nix::libc::{self};
use std::fmt::Debug;
use std::io;
use std::io::Error;
/// Options and flags which can be used to configure how a file is opened.
#[derive(Copy, Clone)]
#[allow(clippy::struct_excessive_bools, reason = "False positive.")]
pub struct OpenOptions {
/// This option, when true, will indicate that the file should be read-able if opened.
read: bool,
/// This option, when true, will indicate that the file should be write-able if opened.
///
/// If the file already exists,
/// any write calls on it will overwrite its contents, without truncating it.
write: bool,
/// This option, when true, means that writes will append
/// to a file instead of overwriting previous contents.
///
/// Note that setting [`write(true)`](OpenOptions::write)[`append(true)`](OpenOptions::append)
/// has the same effect as setting only [`append(true)`](OpenOptions::append).
append: bool,
/// If a file is successfully opened with this option set
/// it will truncate the file to 0 length if it already exists.
///
/// The file must be opened with write access for truncate to work.
truncate: bool,
/// This option, when true, will indicate that the file should be created if it does not exist.
/// Else it will open the file.
///
/// In order for the file to be created,
/// [`OpenOptions::write`](OpenOptions::write)
/// or [`OpenOptions::append`](OpenOptions::append) access must be used.
create: bool,
/// This option, when true, will indicate that the file should be created if it does not exist.
/// Else it will return an [`error`](io::ErrorKind::AlreadyExists).
///
/// For more information, see [`OpenOptions::create_new`](OpenOptions::create_new).
create_new: bool,
/// Pass custom flags to the flags argument of open.
///
/// For more information, see [`OpenOptions::custom_flags`](OpenOptions::custom_flags).
custom_flags: i32,
/// The permissions to apply to the new file.
mode: u32,
}
impl OpenOptions {
/// Creates a blank new set of options ready for configuration.
///
/// All options are initially set to false.
pub const fn new() -> Self {
Self {
read: false,
write: false,
append: false,
truncate: false,
create: false,
create_new: false,
custom_flags: 0,
mode: 0o666,
}
}
/// Sets the option for read access.
///
/// This option, when true, will indicate that the file should be read-able if opened.
#[must_use]
pub fn read(mut self, read: bool) -> Self {
self.read = read;
self
}
/// Sets the option for write access.
///
/// This option, when true, will indicate that the file should be write-able if opened.
///
/// If the file already exists, any write calls on it will overwrite its contents,
/// without truncating it.
#[must_use]
pub fn write(mut self, write: bool) -> Self {
self.write = write;
self
}
/// Sets the option for the append mode.
///
/// This option, when true, means that writes will append to a file
/// instead of overwriting previous contents.
/// Note that setting [`write(true)`](OpenOptions::write)[`append(true)`](OpenOptions::append)
/// has the same effect as setting only [`append(true)`](OpenOptions::append).
#[must_use]
pub fn append(mut self, append: bool) -> Self {
self.append = append;
self
}
/// Sets the option for truncating a previous file.
///
/// If a file is successfully opened with this option set it will truncate
/// the file to 0 length if it already exists.
///
/// The file must be opened with write access for truncate to work.
#[must_use]
pub fn truncate(mut self, truncate: bool) -> Self {
self.truncate = truncate;
self
}
/// Sets the option to create a new file, or open it if it already exists.
/// In order for the file to be created,
/// [`OpenOptions::write`] or [`OpenOptions::append`] access must be used.
#[must_use]
pub fn create(mut self, create: bool) -> Self {
self.create = create;
self
}
/// Sets the option to create a new file, failing if it already exists.
///
/// If a file exists at the target location, creating a new file will fail
/// with [`AlreadyExists`](io::ErrorKind::AlreadyExists) or another error based on the situation.
///
/// This option is useful because it is atomic.
/// Otherwise, between checking whether a file exists and creating a new one,
/// the file may have been created by another process (a TOCTOU race condition / attack).
///
/// If [`create_new(true)`](OpenOptions::create_new) is set, [`create()`](OpenOptions::create)
/// and [`truncate()`](OpenOptions::truncate) are ignored.
///
/// The file must be opened with write or append access in order to create a new file.
#[must_use]
pub fn create_new(mut self, create_new: bool) -> Self {
self.create_new = create_new;
self
}
/// Pass custom flags to the flags argument of open.
///
/// The bits that define the access mode are masked out with `O_ACCMODE`,
/// to ensure they do not interfere with the access mode set by Rusts options.
///
/// Custom flags can only set flags, not remove flags set by Rusts options.
/// This options overwrites any previously set custom flags.
#[must_use]
pub fn custom_flags(mut self, flags: i32) -> Self {
self.custom_flags = flags;
self
}
/// Sets the permissions to apply to the new file.
#[must_use]
pub fn mode(mut self, mode: u32) -> Self {
self.mode = mode;
self
}
#[cfg(unix)]
/// Converts the `OpenOptions` into the argument to `open()` provided by the os.
pub(crate) fn into_os_options(mut self) -> io::Result<OpenHow> {
let access_mode = match (self.read, self.write, self.append) {
(true, false, false) => libc::O_RDONLY,
(false, true, false) => libc::O_WRONLY,
(true, true, false) => libc::O_RDWR,
(false, _, true) => libc::O_WRONLY | libc::O_APPEND,
(true, _, true) => libc::O_RDWR | libc::O_APPEND,
(false, false, false) => return Err(Error::from_raw_os_error(libc::EINVAL)),
};
let creation_flags = match (self.create, self.truncate, self.create_new) {
(false, false, false) => {
self.mode = 0;
0
}
(false, true, false) => {
self.mode = 0;
libc::O_TRUNC
}
(true, false, false) => libc::O_CREAT,
(true, true, false) => libc::O_CREAT | libc::O_TRUNC,
(_, _, true) => libc::O_CREAT | libc::O_EXCL,
};
#[allow(clippy::cast_sign_loss, reason = "Flags don't have signs.")]
Ok(OpenHow::new()
.flags(
(libc::O_CLOEXEC
| access_mode
| creation_flags
| (self.custom_flags & !libc::O_ACCMODE)) as u64,
)
.mode(self.mode.into()))
}
}
impl Default for OpenOptions {
fn default() -> Self {
Self::new()
}
}
impl Debug for OpenOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OpenOptions")
.field("read", &self.read)
.field("write", &self.write)
.field("append", &self.append)
.field("truncate", &self.truncate)
.field("create", &self.create)
.field("create_new", &self.create_new)
.field("custom_flags", &self.custom_flags)
.field("mode", &self.mode)
.finish()
}
}