1use crate::errno::Errno;
2use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
3use std::ffi::OsString;
4#[cfg(not(target_os = "redox"))]
5use std::os::raw;
6use std::os::unix::ffi::OsStringExt;
7use 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))]
14use std::{
15 os::unix::io::{AsFd, AsRawFd},
16 ptr,
17};
18
19#[cfg(feature = "fs")]
20use 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")]
32pub use self::posix_fadvise::{posix_fadvise, PosixFadviseAdvice};
33
34#[cfg(not(target_os = "redox"))]
35#[cfg(any(feature = "fs", feature = "process"))]
36libc_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"))]
52libc_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
202feature! {
203#![feature = "fs"]
204
205// The conversion is not identical on all operating systems.
206#[allow(clippy::useless_conversion)]
207pub 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"))]
222pub 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"))]
235pub 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")]
257libc_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
266feature! {
267#![feature = "fs"]
268#[cfg(all(target_os = "linux", target_env = "gnu"))]
269pub 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
290fn 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
296fn 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
321fn 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
403pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
404 inner_readlink(None, path)
405}
406
407#[cfg(not(target_os = "redox"))]
408pub 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"))]
417pub(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")]
427libc_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")]
443libc_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
452feature! {
453#![feature = "fs"]
454
455#[cfg(not(target_os = "redox"))]
456#[derive(Debug, Eq, Hash, PartialEq)]
457#[non_exhaustive]
458pub 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]
498pub 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}
506pub use self::FcntlArg::*;
507
508// TODO: Figure out how to handle value fcntl returns
509pub 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]
561pub 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")))]
571pub 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")]
595libc_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
616feature! {
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"))]
640pub 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"))]
686pub 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"))]
708pub 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"))]
719pub 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")]
738libc_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
769feature! {
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")]
778pub 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)]
792pub struct SpacectlRange(pub libc::off_t, pub libc::off_t);
793
794#[cfg(any(target_os = "freebsd"))]
795impl 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")]
852pub 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")]
900pub 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))]
933mod 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))]
981pub 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