1 | use crate::errno::Errno; |
2 | use libc::{self, c_char, c_int, c_uint, size_t, ssize_t}; |
3 | use std::ffi::OsString; |
4 | #[cfg (not(target_os = "redox" ))] |
5 | use std::os::raw; |
6 | use std::os::unix::ffi::OsStringExt; |
7 | use std::os::unix::io::RawFd; |
8 | // For splice and copy_file_range |
9 | #[cfg (any( |
10 | target_os = "android" , |
11 | target_os = "freebsd" , |
12 | target_os = "linux" |
13 | ))] |
14 | use std::{ |
15 | os::unix::io::{AsFd, AsRawFd}, |
16 | ptr, |
17 | }; |
18 | |
19 | #[cfg (feature = "fs" )] |
20 | use crate::{sys::stat::Mode, NixPath, Result}; |
21 | |
22 | #[cfg (any( |
23 | target_os = "linux" , |
24 | target_os = "android" , |
25 | target_os = "emscripten" , |
26 | target_os = "fuchsia" , |
27 | target_os = "wasi" , |
28 | target_env = "uclibc" , |
29 | target_os = "freebsd" |
30 | ))] |
31 | #[cfg (feature = "fs" )] |
32 | pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice}; |
33 | |
34 | #[cfg (not(target_os = "redox" ))] |
35 | #[cfg (any(feature = "fs" , feature = "process" ))] |
36 | libc_bitflags! { |
37 | #[cfg_attr (docsrs, doc(cfg(any(feature = "fs" , feature = "process" ))))] |
38 | pub struct AtFlags: c_int { |
39 | AT_REMOVEDIR; |
40 | AT_SYMLINK_FOLLOW; |
41 | AT_SYMLINK_NOFOLLOW; |
42 | #[cfg(any(target_os = "android" , target_os = "linux" ))] |
43 | AT_NO_AUTOMOUNT; |
44 | #[cfg(any(target_os = "android" , target_os = "linux" ))] |
45 | AT_EMPTY_PATH; |
46 | #[cfg(not(target_os = "android" ))] |
47 | AT_EACCESS; |
48 | } |
49 | } |
50 | |
51 | #[cfg (any(feature = "fs" , feature = "term" ))] |
52 | libc_bitflags!( |
53 | /// Configuration options for opened files. |
54 | #[cfg_attr (docsrs, doc(cfg(any(feature = "fs" , feature = "term" ))))] |
55 | pub struct OFlag: c_int { |
56 | /// Mask for the access mode of the file. |
57 | O_ACCMODE; |
58 | /// Use alternate I/O semantics. |
59 | #[cfg(target_os = "netbsd" )] |
60 | #[cfg_attr(docsrs, doc(cfg(all())))] |
61 | O_ALT_IO; |
62 | /// Open the file in append-only mode. |
63 | O_APPEND; |
64 | /// Generate a signal when input or output becomes possible. |
65 | #[cfg(not(any(target_os = "aix" , |
66 | target_os = "illumos" , |
67 | target_os = "solaris" , |
68 | target_os = "haiku" )))] |
69 | #[cfg_attr(docsrs, doc(cfg(all())))] |
70 | O_ASYNC; |
71 | /// Closes the file descriptor once an `execve` call is made. |
72 | /// |
73 | /// Also sets the file offset to the beginning of the file. |
74 | O_CLOEXEC; |
75 | /// Create the file if it does not exist. |
76 | O_CREAT; |
77 | /// Try to minimize cache effects of the I/O for this file. |
78 | #[cfg(any(target_os = "android" , |
79 | target_os = "dragonfly" , |
80 | target_os = "freebsd" , |
81 | target_os = "linux" , |
82 | target_os = "netbsd" ))] |
83 | #[cfg_attr(docsrs, doc(cfg(all())))] |
84 | O_DIRECT; |
85 | /// If the specified path isn't a directory, fail. |
86 | #[cfg(not(any(target_os = "illumos" , target_os = "solaris" )))] |
87 | #[cfg_attr(docsrs, doc(cfg(all())))] |
88 | O_DIRECTORY; |
89 | /// Implicitly follow each `write()` with an `fdatasync()`. |
90 | #[cfg(any(target_os = "android" , |
91 | target_os = "ios" , |
92 | target_os = "linux" , |
93 | target_os = "macos" , |
94 | target_os = "netbsd" , |
95 | target_os = "openbsd" ))] |
96 | #[cfg_attr(docsrs, doc(cfg(all())))] |
97 | O_DSYNC; |
98 | /// Error out if a file was not created. |
99 | O_EXCL; |
100 | /// Open for execute only. |
101 | #[cfg(target_os = "freebsd" )] |
102 | #[cfg_attr(docsrs, doc(cfg(all())))] |
103 | O_EXEC; |
104 | /// Open with an exclusive file lock. |
105 | #[cfg(any(target_os = "dragonfly" , |
106 | target_os = "freebsd" , |
107 | target_os = "ios" , |
108 | target_os = "macos" , |
109 | target_os = "netbsd" , |
110 | target_os = "openbsd" , |
111 | target_os = "redox" ))] |
112 | #[cfg_attr(docsrs, doc(cfg(all())))] |
113 | O_EXLOCK; |
114 | /// Same as `O_SYNC`. |
115 | #[cfg(any(target_os = "dragonfly" , |
116 | target_os = "freebsd" , |
117 | target_os = "ios" , |
118 | all(target_os = "linux" , not(target_env = "musl" )), |
119 | target_os = "macos" , |
120 | target_os = "netbsd" , |
121 | target_os = "openbsd" , |
122 | target_os = "redox" ))] |
123 | #[cfg_attr(docsrs, doc(cfg(all())))] |
124 | O_FSYNC; |
125 | /// Allow files whose sizes can't be represented in an `off_t` to be opened. |
126 | #[cfg(any(target_os = "android" , target_os = "linux" ))] |
127 | #[cfg_attr(docsrs, doc(cfg(all())))] |
128 | O_LARGEFILE; |
129 | /// Do not update the file last access time during `read(2)`s. |
130 | #[cfg(any(target_os = "android" , target_os = "linux" ))] |
131 | #[cfg_attr(docsrs, doc(cfg(all())))] |
132 | O_NOATIME; |
133 | /// Don't attach the device as the process' controlling terminal. |
134 | #[cfg(not(target_os = "redox" ))] |
135 | #[cfg_attr(docsrs, doc(cfg(all())))] |
136 | O_NOCTTY; |
137 | /// Same as `O_NONBLOCK`. |
138 | #[cfg(not(any(target_os = "redox" , target_os = "haiku" )))] |
139 | #[cfg_attr(docsrs, doc(cfg(all())))] |
140 | O_NDELAY; |
141 | /// `open()` will fail if the given path is a symbolic link. |
142 | O_NOFOLLOW; |
143 | /// When possible, open the file in nonblocking mode. |
144 | O_NONBLOCK; |
145 | /// Don't deliver `SIGPIPE`. |
146 | #[cfg(target_os = "netbsd" )] |
147 | #[cfg_attr(docsrs, doc(cfg(all())))] |
148 | O_NOSIGPIPE; |
149 | /// Obtain a file descriptor for low-level access. |
150 | /// |
151 | /// The file itself is not opened and other file operations will fail. |
152 | #[cfg(any(target_os = "android" , target_os = "linux" , target_os = "redox" ))] |
153 | #[cfg_attr(docsrs, doc(cfg(all())))] |
154 | O_PATH; |
155 | /// Only allow reading. |
156 | /// |
157 | /// This should not be combined with `O_WRONLY` or `O_RDWR`. |
158 | O_RDONLY; |
159 | /// Allow both reading and writing. |
160 | /// |
161 | /// This should not be combined with `O_WRONLY` or `O_RDONLY`. |
162 | O_RDWR; |
163 | /// Similar to `O_DSYNC` but applies to `read`s instead. |
164 | #[cfg(any(target_os = "linux" , target_os = "netbsd" , target_os = "openbsd" ))] |
165 | #[cfg_attr(docsrs, doc(cfg(all())))] |
166 | O_RSYNC; |
167 | /// Skip search permission checks. |
168 | #[cfg(target_os = "netbsd" )] |
169 | #[cfg_attr(docsrs, doc(cfg(all())))] |
170 | O_SEARCH; |
171 | /// Open with a shared file lock. |
172 | #[cfg(any(target_os = "dragonfly" , |
173 | target_os = "freebsd" , |
174 | target_os = "ios" , |
175 | target_os = "macos" , |
176 | target_os = "netbsd" , |
177 | target_os = "openbsd" , |
178 | target_os = "redox" ))] |
179 | #[cfg_attr(docsrs, doc(cfg(all())))] |
180 | O_SHLOCK; |
181 | /// Implicitly follow each `write()` with an `fsync()`. |
182 | #[cfg(not(target_os = "redox" ))] |
183 | #[cfg_attr(docsrs, doc(cfg(all())))] |
184 | O_SYNC; |
185 | /// Create an unnamed temporary file. |
186 | #[cfg(any(target_os = "android" , target_os = "linux" ))] |
187 | #[cfg_attr(docsrs, doc(cfg(all())))] |
188 | O_TMPFILE; |
189 | /// Truncate an existing regular file to 0 length if it allows writing. |
190 | O_TRUNC; |
191 | /// Restore default TTY attributes. |
192 | #[cfg(target_os = "freebsd" )] |
193 | #[cfg_attr(docsrs, doc(cfg(all())))] |
194 | O_TTY_INIT; |
195 | /// Only allow writing. |
196 | /// |
197 | /// This should not be combined with `O_RDONLY` or `O_RDWR`. |
198 | O_WRONLY; |
199 | } |
200 | ); |
201 | |
202 | feature! { |
203 | #![feature = "fs" ] |
204 | |
205 | // The conversion is not identical on all operating systems. |
206 | #[allow (clippy::useless_conversion)] |
207 | pub fn open<P: ?Sized + NixPath>( |
208 | path: &P, |
209 | oflag: OFlag, |
210 | mode: Mode, |
211 | ) -> Result<RawFd> { |
212 | let fd = path.with_nix_path(|cstr| unsafe { |
213 | libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) |
214 | })?; |
215 | |
216 | Errno::result(fd) |
217 | } |
218 | |
219 | // The conversion is not identical on all operating systems. |
220 | #[allow (clippy::useless_conversion)] |
221 | #[cfg (not(target_os = "redox" ))] |
222 | pub fn openat<P: ?Sized + NixPath>( |
223 | dirfd: RawFd, |
224 | path: &P, |
225 | oflag: OFlag, |
226 | mode: Mode, |
227 | ) -> Result<RawFd> { |
228 | let fd = path.with_nix_path(|cstr| unsafe { |
229 | libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) |
230 | })?; |
231 | Errno::result(fd) |
232 | } |
233 | |
234 | #[cfg (not(target_os = "redox" ))] |
235 | pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( |
236 | old_dirfd: Option<RawFd>, |
237 | old_path: &P1, |
238 | new_dirfd: Option<RawFd>, |
239 | new_path: &P2, |
240 | ) -> Result<()> { |
241 | let res = old_path.with_nix_path(|old_cstr| { |
242 | new_path.with_nix_path(|new_cstr| unsafe { |
243 | libc::renameat( |
244 | at_rawfd(old_dirfd), |
245 | old_cstr.as_ptr(), |
246 | at_rawfd(new_dirfd), |
247 | new_cstr.as_ptr(), |
248 | ) |
249 | }) |
250 | })??; |
251 | Errno::result(res).map(drop) |
252 | } |
253 | } |
254 | |
255 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
256 | #[cfg (feature = "fs" )] |
257 | libc_bitflags! { |
258 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
259 | pub struct RenameFlags: u32 { |
260 | RENAME_EXCHANGE; |
261 | RENAME_NOREPLACE; |
262 | RENAME_WHITEOUT; |
263 | } |
264 | } |
265 | |
266 | feature! { |
267 | #![feature = "fs" ] |
268 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
269 | pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>( |
270 | old_dirfd: Option<RawFd>, |
271 | old_path: &P1, |
272 | new_dirfd: Option<RawFd>, |
273 | new_path: &P2, |
274 | flags: RenameFlags, |
275 | ) -> Result<()> { |
276 | let res = old_path.with_nix_path(|old_cstr| { |
277 | new_path.with_nix_path(|new_cstr| unsafe { |
278 | libc::renameat2( |
279 | at_rawfd(old_dirfd), |
280 | old_cstr.as_ptr(), |
281 | at_rawfd(new_dirfd), |
282 | new_cstr.as_ptr(), |
283 | flags.bits(), |
284 | ) |
285 | }) |
286 | })??; |
287 | Errno::result(res).map(drop) |
288 | } |
289 | |
290 | fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> { |
291 | unsafe { v.set_len(len as usize) } |
292 | v.shrink_to_fit(); |
293 | Ok(OsString::from_vec(v.to_vec())) |
294 | } |
295 | |
296 | fn readlink_maybe_at<P: ?Sized + NixPath>( |
297 | dirfd: Option<RawFd>, |
298 | path: &P, |
299 | v: &mut Vec<u8>, |
300 | ) -> Result<libc::ssize_t> { |
301 | path.with_nix_path(|cstr| unsafe { |
302 | match dirfd { |
303 | #[cfg (target_os = "redox" )] |
304 | Some(_) => unreachable!(), |
305 | #[cfg (not(target_os = "redox" ))] |
306 | Some(dirfd) => libc::readlinkat( |
307 | dirfd, |
308 | cstr.as_ptr(), |
309 | v.as_mut_ptr() as *mut c_char, |
310 | v.capacity() as size_t, |
311 | ), |
312 | None => libc::readlink( |
313 | cstr.as_ptr(), |
314 | v.as_mut_ptr() as *mut c_char, |
315 | v.capacity() as size_t, |
316 | ), |
317 | } |
318 | }) |
319 | } |
320 | |
321 | fn inner_readlink<P: ?Sized + NixPath>( |
322 | dirfd: Option<RawFd>, |
323 | path: &P, |
324 | ) -> Result<OsString> { |
325 | let mut v = Vec::with_capacity(libc::PATH_MAX as usize); |
326 | |
327 | { |
328 | // simple case: result is strictly less than `PATH_MAX` |
329 | let res = readlink_maybe_at(dirfd, path, &mut v)?; |
330 | let len = Errno::result(res)?; |
331 | debug_assert!(len >= 0); |
332 | if (len as usize) < v.capacity() { |
333 | return wrap_readlink_result(v, res); |
334 | } |
335 | } |
336 | |
337 | // Uh oh, the result is too long... |
338 | // Let's try to ask lstat how many bytes to allocate. |
339 | let mut try_size = { |
340 | let reported_size = match dirfd { |
341 | #[cfg (target_os = "redox" )] |
342 | Some(_) => unreachable!(), |
343 | #[cfg (any(target_os = "android" , target_os = "linux" ))] |
344 | Some(dirfd) => { |
345 | let flags = if path.is_empty() { |
346 | AtFlags::AT_EMPTY_PATH |
347 | } else { |
348 | AtFlags::empty() |
349 | }; |
350 | super::sys::stat::fstatat( |
351 | dirfd, |
352 | path, |
353 | flags | AtFlags::AT_SYMLINK_NOFOLLOW, |
354 | ) |
355 | } |
356 | #[cfg (not(any( |
357 | target_os = "android" , |
358 | target_os = "linux" , |
359 | target_os = "redox" |
360 | )))] |
361 | Some(dirfd) => super::sys::stat::fstatat( |
362 | dirfd, |
363 | path, |
364 | AtFlags::AT_SYMLINK_NOFOLLOW, |
365 | ), |
366 | None => super::sys::stat::lstat(path), |
367 | } |
368 | .map(|x| x.st_size) |
369 | .unwrap_or(0); |
370 | |
371 | if reported_size > 0 { |
372 | // Note: even if `lstat`'s apparently valid answer turns out to be |
373 | // wrong, we will still read the full symlink no matter what. |
374 | reported_size as usize + 1 |
375 | } else { |
376 | // If lstat doesn't cooperate, or reports an error, be a little less |
377 | // precise. |
378 | (libc::PATH_MAX as usize).max(128) << 1 |
379 | } |
380 | }; |
381 | |
382 | loop { |
383 | { |
384 | v.reserve_exact(try_size); |
385 | let res = readlink_maybe_at(dirfd, path, &mut v)?; |
386 | let len = Errno::result(res)?; |
387 | debug_assert!(len >= 0); |
388 | if (len as usize) < v.capacity() { |
389 | return wrap_readlink_result(v, res); |
390 | } |
391 | } |
392 | |
393 | // Ugh! Still not big enough! |
394 | match try_size.checked_shl(1) { |
395 | Some(next_size) => try_size = next_size, |
396 | // It's absurd that this would happen, but handle it sanely |
397 | // anyway. |
398 | None => break Err(Errno::ENAMETOOLONG), |
399 | } |
400 | } |
401 | } |
402 | |
403 | pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> { |
404 | inner_readlink(None, path) |
405 | } |
406 | |
407 | #[cfg (not(target_os = "redox" ))] |
408 | pub fn readlinkat<P: ?Sized + NixPath>( |
409 | dirfd: RawFd, |
410 | path: &P, |
411 | ) -> Result<OsString> { |
412 | inner_readlink(Some(dirfd), path) |
413 | } |
414 | |
415 | /// Computes the raw fd consumed by a function of the form `*at`. |
416 | #[cfg (not(target_os = "redox" ))] |
417 | pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int { |
418 | match fd { |
419 | None => libc::AT_FDCWD, |
420 | Some(fd) => fd, |
421 | } |
422 | } |
423 | } |
424 | |
425 | #[cfg (any(target_os = "android" , target_os = "linux" , target_os = "freebsd" ))] |
426 | #[cfg (feature = "fs" )] |
427 | libc_bitflags!( |
428 | /// Additional flags for file sealing, which allows for limiting operations on a file. |
429 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
430 | pub struct SealFlag: c_int { |
431 | /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`. |
432 | F_SEAL_SEAL; |
433 | /// The file cannot be reduced in size. |
434 | F_SEAL_SHRINK; |
435 | /// The size of the file cannot be increased. |
436 | F_SEAL_GROW; |
437 | /// The file contents cannot be modified. |
438 | F_SEAL_WRITE; |
439 | } |
440 | ); |
441 | |
442 | #[cfg (feature = "fs" )] |
443 | libc_bitflags!( |
444 | /// Additional configuration flags for `fcntl`'s `F_SETFD`. |
445 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
446 | pub struct FdFlag: c_int { |
447 | /// The file descriptor will automatically be closed during a successful `execve(2)`. |
448 | FD_CLOEXEC; |
449 | } |
450 | ); |
451 | |
452 | feature! { |
453 | #![feature = "fs" ] |
454 | |
455 | #[cfg (not(target_os = "redox" ))] |
456 | #[derive (Debug, Eq, Hash, PartialEq)] |
457 | #[non_exhaustive ] |
458 | pub enum FcntlArg<'a> { |
459 | F_DUPFD(RawFd), |
460 | F_DUPFD_CLOEXEC(RawFd), |
461 | F_GETFD, |
462 | F_SETFD(FdFlag), // FD_FLAGS |
463 | F_GETFL, |
464 | F_SETFL(OFlag), // O_NONBLOCK |
465 | F_SETLK(&'a libc::flock), |
466 | F_SETLKW(&'a libc::flock), |
467 | F_GETLK(&'a mut libc::flock), |
468 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
469 | F_OFD_SETLK(&'a libc::flock), |
470 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
471 | F_OFD_SETLKW(&'a libc::flock), |
472 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
473 | F_OFD_GETLK(&'a mut libc::flock), |
474 | #[cfg (any( |
475 | target_os = "android" , |
476 | target_os = "linux" , |
477 | target_os = "freebsd" |
478 | ))] |
479 | F_ADD_SEALS(SealFlag), |
480 | #[cfg (any( |
481 | target_os = "android" , |
482 | target_os = "linux" , |
483 | target_os = "freebsd" |
484 | ))] |
485 | F_GET_SEALS, |
486 | #[cfg (any(target_os = "macos" , target_os = "ios" ))] |
487 | F_FULLFSYNC, |
488 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
489 | F_GETPIPE_SZ, |
490 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
491 | F_SETPIPE_SZ(c_int), |
492 | // TODO: Rest of flags |
493 | } |
494 | |
495 | #[cfg (target_os = "redox" )] |
496 | #[derive (Debug, Clone, Copy, Eq, Hash, PartialEq)] |
497 | #[non_exhaustive ] |
498 | pub enum FcntlArg { |
499 | F_DUPFD(RawFd), |
500 | F_DUPFD_CLOEXEC(RawFd), |
501 | F_GETFD, |
502 | F_SETFD(FdFlag), // FD_FLAGS |
503 | F_GETFL, |
504 | F_SETFL(OFlag), // O_NONBLOCK |
505 | } |
506 | pub use self::FcntlArg::*; |
507 | |
508 | // TODO: Figure out how to handle value fcntl returns |
509 | pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> { |
510 | let res = unsafe { |
511 | match arg { |
512 | F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd), |
513 | F_DUPFD_CLOEXEC(rawfd) => { |
514 | libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd) |
515 | } |
516 | F_GETFD => libc::fcntl(fd, libc::F_GETFD), |
517 | F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()), |
518 | F_GETFL => libc::fcntl(fd, libc::F_GETFL), |
519 | F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()), |
520 | #[cfg (not(target_os = "redox" ))] |
521 | F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock), |
522 | #[cfg (not(target_os = "redox" ))] |
523 | F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock), |
524 | #[cfg (not(target_os = "redox" ))] |
525 | F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock), |
526 | #[cfg (any(target_os = "android" , target_os = "linux" ))] |
527 | F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock), |
528 | #[cfg (any(target_os = "android" , target_os = "linux" ))] |
529 | F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock), |
530 | #[cfg (any(target_os = "android" , target_os = "linux" ))] |
531 | F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock), |
532 | #[cfg (any( |
533 | target_os = "android" , |
534 | target_os = "linux" , |
535 | target_os = "freebsd" |
536 | ))] |
537 | F_ADD_SEALS(flag) => { |
538 | libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()) |
539 | } |
540 | #[cfg (any( |
541 | target_os = "android" , |
542 | target_os = "linux" , |
543 | target_os = "freebsd" |
544 | ))] |
545 | F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS), |
546 | #[cfg (any(target_os = "macos" , target_os = "ios" ))] |
547 | F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC), |
548 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
549 | F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ), |
550 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
551 | F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size), |
552 | } |
553 | }; |
554 | |
555 | Errno::result(res) |
556 | } |
557 | |
558 | // TODO: convert to libc_enum |
559 | #[derive (Clone, Copy, Debug, Eq, Hash, PartialEq)] |
560 | #[non_exhaustive ] |
561 | pub enum FlockArg { |
562 | LockShared, |
563 | LockExclusive, |
564 | Unlock, |
565 | LockSharedNonblock, |
566 | LockExclusiveNonblock, |
567 | UnlockNonblock, |
568 | } |
569 | |
570 | #[cfg (not(any(target_os = "redox" , target_os = "solaris" )))] |
571 | pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> { |
572 | use self::FlockArg::*; |
573 | |
574 | let res = unsafe { |
575 | match arg { |
576 | LockShared => libc::flock(fd, libc::LOCK_SH), |
577 | LockExclusive => libc::flock(fd, libc::LOCK_EX), |
578 | Unlock => libc::flock(fd, libc::LOCK_UN), |
579 | LockSharedNonblock => { |
580 | libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB) |
581 | } |
582 | LockExclusiveNonblock => { |
583 | libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) |
584 | } |
585 | UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB), |
586 | } |
587 | }; |
588 | |
589 | Errno::result(res).map(drop) |
590 | } |
591 | } |
592 | |
593 | #[cfg (any(target_os = "android" , target_os = "linux" ))] |
594 | #[cfg (feature = "zerocopy" )] |
595 | libc_bitflags! { |
596 | /// Additional flags to `splice` and friends. |
597 | #[cfg_attr (docsrs, doc(cfg(feature = "zerocopy" )))] |
598 | pub struct SpliceFFlags: c_uint { |
599 | /// Request that pages be moved instead of copied. |
600 | /// |
601 | /// Not applicable to `vmsplice`. |
602 | SPLICE_F_MOVE; |
603 | /// Do not block on I/O. |
604 | SPLICE_F_NONBLOCK; |
605 | /// Hint that more data will be coming in a subsequent splice. |
606 | /// |
607 | /// Not applicable to `vmsplice`. |
608 | SPLICE_F_MORE; |
609 | /// Gift the user pages to the kernel. |
610 | /// |
611 | /// Not applicable to `splice`. |
612 | SPLICE_F_GIFT; |
613 | } |
614 | } |
615 | |
616 | feature! { |
617 | #![feature = "zerocopy" ] |
618 | |
619 | /// Copy a range of data from one file to another |
620 | /// |
621 | /// The `copy_file_range` system call performs an in-kernel copy between |
622 | /// file descriptors `fd_in` and `fd_out` without the additional cost of |
623 | /// transferring data from the kernel to user space and back again. There may be |
624 | /// additional optimizations for specific file systems. It copies up to `len` |
625 | /// bytes of data from file descriptor `fd_in` to file descriptor `fd_out`, |
626 | /// overwriting any data that exists within the requested range of the target |
627 | /// file. |
628 | /// |
629 | /// If the `off_in` and/or `off_out` arguments are used, the values |
630 | /// will be mutated to reflect the new position within the file after |
631 | /// copying. If they are not used, the relevant file descriptors will be seeked |
632 | /// to the new position. |
633 | /// |
634 | /// On successful completion the number of bytes actually copied will be |
635 | /// returned. |
636 | // Note: FreeBSD defines the offset argument as "off_t". Linux and Android |
637 | // define it as "loff_t". But on both OSes, on all supported platforms, those |
638 | // are 64 bits. So Nix uses i64 to make the docs simple and consistent. |
639 | #[cfg (any(target_os = "android" , target_os = "freebsd" , target_os = "linux" ))] |
640 | pub fn copy_file_range<Fd1: AsFd, Fd2: AsFd>( |
641 | fd_in: Fd1, |
642 | off_in: Option<&mut i64>, |
643 | fd_out: Fd2, |
644 | off_out: Option<&mut i64>, |
645 | len: usize, |
646 | ) -> Result<usize> { |
647 | let off_in = off_in |
648 | .map(|offset| offset as *mut i64) |
649 | .unwrap_or(ptr::null_mut()); |
650 | let off_out = off_out |
651 | .map(|offset| offset as *mut i64) |
652 | .unwrap_or(ptr::null_mut()); |
653 | |
654 | cfg_if::cfg_if! { |
655 | if #[cfg(target_os = "freebsd" )] { |
656 | let ret = unsafe { |
657 | libc::copy_file_range( |
658 | fd_in.as_fd().as_raw_fd(), |
659 | off_in, |
660 | fd_out.as_fd().as_raw_fd(), |
661 | off_out, |
662 | len, |
663 | 0, |
664 | ) |
665 | }; |
666 | } else { |
667 | // May Linux distros still don't include copy_file_range in their |
668 | // libc implementations, so we need to make a direct syscall. |
669 | let ret = unsafe { |
670 | libc::syscall( |
671 | libc::SYS_copy_file_range, |
672 | fd_in, |
673 | off_in, |
674 | fd_out.as_fd().as_raw_fd(), |
675 | off_out, |
676 | len, |
677 | 0, |
678 | ) |
679 | }; |
680 | } |
681 | } |
682 | Errno::result(ret).map(|r| r as usize) |
683 | } |
684 | |
685 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
686 | pub fn splice( |
687 | fd_in: RawFd, |
688 | off_in: Option<&mut libc::loff_t>, |
689 | fd_out: RawFd, |
690 | off_out: Option<&mut libc::loff_t>, |
691 | len: usize, |
692 | flags: SpliceFFlags, |
693 | ) -> Result<usize> { |
694 | let off_in = off_in |
695 | .map(|offset| offset as *mut libc::loff_t) |
696 | .unwrap_or(ptr::null_mut()); |
697 | let off_out = off_out |
698 | .map(|offset| offset as *mut libc::loff_t) |
699 | .unwrap_or(ptr::null_mut()); |
700 | |
701 | let ret = unsafe { |
702 | libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) |
703 | }; |
704 | Errno::result(ret).map(|r| r as usize) |
705 | } |
706 | |
707 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
708 | pub fn tee( |
709 | fd_in: RawFd, |
710 | fd_out: RawFd, |
711 | len: usize, |
712 | flags: SpliceFFlags, |
713 | ) -> Result<usize> { |
714 | let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) }; |
715 | Errno::result(ret).map(|r| r as usize) |
716 | } |
717 | |
718 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
719 | pub fn vmsplice( |
720 | fd: RawFd, |
721 | iov: &[std::io::IoSlice<'_>], |
722 | flags: SpliceFFlags, |
723 | ) -> Result<usize> { |
724 | let ret = unsafe { |
725 | libc::vmsplice( |
726 | fd, |
727 | iov.as_ptr() as *const libc::iovec, |
728 | iov.len(), |
729 | flags.bits(), |
730 | ) |
731 | }; |
732 | Errno::result(ret).map(|r| r as usize) |
733 | } |
734 | } |
735 | |
736 | #[cfg (target_os = "linux" )] |
737 | #[cfg (feature = "fs" )] |
738 | libc_bitflags!( |
739 | /// Mode argument flags for fallocate determining operation performed on a given range. |
740 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
741 | pub struct FallocateFlags: c_int { |
742 | /// File size is not changed. |
743 | /// |
744 | /// offset + len can be greater than file size. |
745 | FALLOC_FL_KEEP_SIZE; |
746 | /// Deallocates space by creating a hole. |
747 | /// |
748 | /// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes. |
749 | FALLOC_FL_PUNCH_HOLE; |
750 | /// Removes byte range from a file without leaving a hole. |
751 | /// |
752 | /// Byte range to collapse starts at offset and continues for len bytes. |
753 | FALLOC_FL_COLLAPSE_RANGE; |
754 | /// Zeroes space in specified byte range. |
755 | /// |
756 | /// Byte range starts at offset and continues for len bytes. |
757 | FALLOC_FL_ZERO_RANGE; |
758 | /// Increases file space by inserting a hole within the file size. |
759 | /// |
760 | /// Does not overwrite existing data. Hole starts at offset and continues for len bytes. |
761 | FALLOC_FL_INSERT_RANGE; |
762 | /// Shared file data extants are made private to the file. |
763 | /// |
764 | /// Gaurantees that a subsequent write will not fail due to lack of space. |
765 | FALLOC_FL_UNSHARE_RANGE; |
766 | } |
767 | ); |
768 | |
769 | feature! { |
770 | #![feature = "fs" ] |
771 | |
772 | /// Manipulates file space. |
773 | /// |
774 | /// Allows the caller to directly manipulate the allocated disk space for the |
775 | /// file referred to by fd. |
776 | #[cfg (target_os = "linux" )] |
777 | #[cfg (feature = "fs" )] |
778 | pub fn fallocate( |
779 | fd: RawFd, |
780 | mode: FallocateFlags, |
781 | offset: libc::off_t, |
782 | len: libc::off_t, |
783 | ) -> Result<()> { |
784 | let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) }; |
785 | Errno::result(res).map(drop) |
786 | } |
787 | |
788 | /// Argument to [`fspacectl`] describing the range to zero. The first member is |
789 | /// the file offset, and the second is the length of the region. |
790 | #[cfg (any(target_os = "freebsd" ))] |
791 | #[derive (Clone, Copy, Debug, Eq, PartialEq)] |
792 | pub struct SpacectlRange(pub libc::off_t, pub libc::off_t); |
793 | |
794 | #[cfg (any(target_os = "freebsd" ))] |
795 | impl SpacectlRange { |
796 | #[inline ] |
797 | pub fn is_empty(&self) -> bool { |
798 | self.1 == 0 |
799 | } |
800 | |
801 | #[inline ] |
802 | pub fn len(&self) -> libc::off_t { |
803 | self.1 |
804 | } |
805 | |
806 | #[inline ] |
807 | pub fn offset(&self) -> libc::off_t { |
808 | self.0 |
809 | } |
810 | } |
811 | |
812 | /// Punch holes in a file. |
813 | /// |
814 | /// `fspacectl` instructs the file system to deallocate a portion of a file. |
815 | /// After a successful operation, this region of the file will return all zeroes |
816 | /// if read. If the file system supports deallocation, then it may free the |
817 | /// underlying storage, too. |
818 | /// |
819 | /// # Arguments |
820 | /// |
821 | /// - `fd` - File to operate on |
822 | /// - `range.0` - File offset at which to begin deallocation |
823 | /// - `range.1` - Length of the region to deallocate |
824 | /// |
825 | /// # Returns |
826 | /// |
827 | /// The operation may deallocate less than the entire requested region. On |
828 | /// success, it returns the region that still remains to be deallocated. The |
829 | /// caller should loop until the returned region is empty. |
830 | /// |
831 | /// # Example |
832 | /// |
833 | #[cfg_attr (fbsd14, doc = " ```" )] |
834 | #[cfg_attr (not(fbsd14), doc = " ```no_run" )] |
835 | /// # use std::io::Write; |
836 | /// # use std::os::unix::fs::FileExt; |
837 | /// # use std::os::unix::io::AsRawFd; |
838 | /// # use nix::fcntl::*; |
839 | /// # use tempfile::tempfile; |
840 | /// const INITIAL: &[u8] = b"0123456789abcdef"; |
841 | /// let mut f = tempfile().unwrap(); |
842 | /// f.write_all(INITIAL).unwrap(); |
843 | /// let mut range = SpacectlRange(3, 6); |
844 | /// while (!range.is_empty()) { |
845 | /// range = fspacectl(f.as_raw_fd(), range).unwrap(); |
846 | /// } |
847 | /// let mut buf = vec![0; INITIAL.len()]; |
848 | /// f.read_exact_at(&mut buf, 0).unwrap(); |
849 | /// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); |
850 | /// ``` |
851 | #[cfg (target_os = "freebsd" )] |
852 | pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result<SpacectlRange> { |
853 | let mut rqsr = libc::spacectl_range { |
854 | r_offset: range.0, |
855 | r_len: range.1, |
856 | }; |
857 | let res = unsafe { |
858 | libc::fspacectl( |
859 | fd, |
860 | libc::SPACECTL_DEALLOC, // Only one command is supported ATM |
861 | &rqsr, |
862 | 0, // No flags are currently supported |
863 | &mut rqsr, |
864 | ) |
865 | }; |
866 | Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len)) |
867 | } |
868 | |
869 | /// Like [`fspacectl`], but will never return incomplete. |
870 | /// |
871 | /// # Arguments |
872 | /// |
873 | /// - `fd` - File to operate on |
874 | /// - `offset` - File offset at which to begin deallocation |
875 | /// - `len` - Length of the region to deallocate |
876 | /// |
877 | /// # Returns |
878 | /// |
879 | /// Returns `()` on success. On failure, the region may or may not be partially |
880 | /// deallocated. |
881 | /// |
882 | /// # Example |
883 | /// |
884 | #[cfg_attr (fbsd14, doc = " ```" )] |
885 | #[cfg_attr (not(fbsd14), doc = " ```no_run" )] |
886 | /// # use std::io::Write; |
887 | /// # use std::os::unix::fs::FileExt; |
888 | /// # use std::os::unix::io::AsRawFd; |
889 | /// # use nix::fcntl::*; |
890 | /// # use tempfile::tempfile; |
891 | /// const INITIAL: &[u8] = b"0123456789abcdef"; |
892 | /// let mut f = tempfile().unwrap(); |
893 | /// f.write_all(INITIAL).unwrap(); |
894 | /// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap(); |
895 | /// let mut buf = vec![0; INITIAL.len()]; |
896 | /// f.read_exact_at(&mut buf, 0).unwrap(); |
897 | /// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef"); |
898 | /// ``` |
899 | #[cfg (target_os = "freebsd" )] |
900 | pub fn fspacectl_all( |
901 | fd: RawFd, |
902 | offset: libc::off_t, |
903 | len: libc::off_t, |
904 | ) -> Result<()> { |
905 | let mut rqsr = libc::spacectl_range { |
906 | r_offset: offset, |
907 | r_len: len, |
908 | }; |
909 | while rqsr.r_len > 0 { |
910 | let res = unsafe { |
911 | libc::fspacectl( |
912 | fd, |
913 | libc::SPACECTL_DEALLOC, // Only one command is supported ATM |
914 | &rqsr, |
915 | 0, // No flags are currently supported |
916 | &mut rqsr, |
917 | ) |
918 | }; |
919 | Errno::result(res)?; |
920 | } |
921 | Ok(()) |
922 | } |
923 | |
924 | #[cfg (any( |
925 | target_os = "linux" , |
926 | target_os = "android" , |
927 | target_os = "emscripten" , |
928 | target_os = "fuchsia" , |
929 | target_os = "wasi" , |
930 | target_env = "uclibc" , |
931 | target_os = "freebsd" |
932 | ))] |
933 | mod posix_fadvise { |
934 | use crate::errno::Errno; |
935 | use crate::Result; |
936 | use std::os::unix::io::RawFd; |
937 | |
938 | #[cfg (feature = "fs" )] |
939 | libc_enum! { |
940 | #[repr (i32)] |
941 | #[non_exhaustive ] |
942 | #[cfg_attr (docsrs, doc(cfg(feature = "fs" )))] |
943 | pub enum PosixFadviseAdvice { |
944 | POSIX_FADV_NORMAL, |
945 | POSIX_FADV_SEQUENTIAL, |
946 | POSIX_FADV_RANDOM, |
947 | POSIX_FADV_NOREUSE, |
948 | POSIX_FADV_WILLNEED, |
949 | POSIX_FADV_DONTNEED, |
950 | } |
951 | } |
952 | |
953 | feature! { |
954 | #![feature = "fs" ] |
955 | pub fn posix_fadvise( |
956 | fd: RawFd, |
957 | offset: libc::off_t, |
958 | len: libc::off_t, |
959 | advice: PosixFadviseAdvice, |
960 | ) -> Result<()> { |
961 | let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) }; |
962 | |
963 | if res == 0 { |
964 | Ok(()) |
965 | } else { |
966 | Err(Errno::from_i32(res)) |
967 | } |
968 | } |
969 | } |
970 | } |
971 | |
972 | #[cfg (any( |
973 | target_os = "linux" , |
974 | target_os = "android" , |
975 | target_os = "dragonfly" , |
976 | target_os = "emscripten" , |
977 | target_os = "fuchsia" , |
978 | target_os = "wasi" , |
979 | target_os = "freebsd" |
980 | ))] |
981 | pub fn posix_fallocate( |
982 | fd: RawFd, |
983 | offset: libc::off_t, |
984 | len: libc::off_t, |
985 | ) -> Result<()> { |
986 | let res = unsafe { libc::posix_fallocate(fd, offset, len) }; |
987 | match Errno::result(res) { |
988 | Err(err) => Err(err), |
989 | Ok(0) => Ok(()), |
990 | Ok(errno) => Err(Errno::from_i32(errno)), |
991 | } |
992 | } |
993 | } |
994 | |