openat_ext/
lib.rs

1//! # This crate is deprecated
2//!
3//! This crate is deprecated.  Development has shifted to the
4//! [cap-std](https://docs.rs/cap-std/latest/cap_std/) ecosystem,
5//! and the successor to this crate is [cap-std-ext](https://docs.rs/cap-std-ext/latest/cap_std_ext/).
6//!
7//! # Extension methods for openat::Dir and std::fs::File
8//!
9//! ```
10//! use openat_ext::OpenatDirExt;
11//! ```
12//!
13//! The `openat` crate is a low-level API, generally just exposing
14//! thin wrappers for the underlying system call.  This crate offers
15//! a number of common higher level convenience functions.
16//!
17//! More recently, there is also an `FileExt` available; it currently
18//! just contains an optimized file copy method that will hopefully
19//! go into the standard library.
20
21#![deny(unused_results)]
22#![deny(missing_docs)]
23#![deny(unsafe_code)]
24
25const TEMPFILE_ATTEMPTS: u32 = 100;
26
27use rand::Rng;
28use std::ffi::OsStr;
29use std::fs::File;
30use std::io::prelude::*;
31use std::os::unix::ffi::OsStrExt;
32use std::os::unix::fs::PermissionsExt;
33use std::os::unix::io::AsRawFd;
34use std::os::unix::prelude::FileExt as UnixFileExt;
35use std::path::Path;
36use std::{fs, io};
37
38/// Private helper to retry interruptible syscalls.
39macro_rules! retry_eintr {
40    ($inner:expr) => {
41        loop {
42            let err = match $inner {
43                Err(e) => e,
44                val => break val,
45            };
46
47            if let Some(errno) = err.raw_os_error() {
48                if errno == libc::EINTR {
49                    continue;
50                }
51            }
52
53            break Err(err);
54        }
55    };
56}
57
58/// Helper functions for openat::Dir
59pub trait OpenatDirExt {
60    /// Checking for nonexistent files (`ENOENT`) is by far the most common case of inspecting error
61    /// codes in Unix.  Rust has a nice `Option<>` type, so this helper makes use of it and returns `Ok(None)`
62    /// for nonexistent files.  All other errors are `Err`, and extant files are `Ok(Some<file>))`
63    /// of course.
64    fn open_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<fs::File>>;
65
66    /// Like `std::fs::read_to_string()` but with a path relative to the openat::Dir.
67    fn read_to_string<P: openat::AsPath>(&self, p: P) -> io::Result<String>;
68
69    /// Like `read_to_string`, but returns `Ok(None)` for nonexistent paths.
70    fn read_to_string_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<String>>;
71
72    /// Remove a file from the given directory; does not error if the target does
73    /// not exist.  But will return an error if the target is a directory.
74    fn remove_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
75
76    /// Remove an empty sub-directory from the given directory; does not error if the target does
77    /// not exist.  But will return an error if the target is a file or symlink.
78    fn remove_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
79
80    /// Like `open_file_optional()` except opens a directory via `openat::dir::sub_dir`.
81    fn sub_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Dir>>;
82
83    /// Like `metadata()` except returns `Ok(None)` for nonexistent paths.
84    fn metadata_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Metadata>>;
85
86    /// On modern filesystems the directory entry contains the type; if available,
87    /// return it.  Otherwise invoke `stat()`.
88    fn get_file_type(&self, e: &openat::Entry) -> io::Result<openat::SimpleType>;
89
90    /// Returns true iff file exists (may be a directory or symlink).  Symbolic links
91    /// are not followed.
92    fn exists<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
93
94    /// Create a directory but don't error if it already exists.
95    fn ensure_dir<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()>;
96
97    /// Create directory and all parents as necessary; no error is returned if directory already exists.
98    fn ensure_dir_all<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()>;
99
100    /// Remove all content at the target path, returns `true` if something existed there.
101    fn remove_all<P: openat::AsPath>(&self, p: P) -> io::Result<bool>;
102
103    /// Synchronize to disk the filesystem containing this directory.
104    fn syncfs(&self) -> io::Result<()>;
105
106    /// If `oldpath` exists, rename it to `newpath`. Otherwise do nothing.
107    ///
108    /// This returns `true` if the old path has been succesfully renamed, `false` otherwise.
109    fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
110        &self,
111        oldpath: P,
112        newpath: R,
113    ) -> io::Result<bool>;
114
115    /// Update timestamps (both access and modification) to the current time.
116    ///
117    /// If the entry at `path` is a symlink, its direct timestamps are updated without
118    /// following the link.
119    fn update_timestamps<P: openat::AsPath>(&self, path: P) -> io::Result<()>;
120
121    /// Update permissions for the given path (see `fchmodat(2)`).
122    ///
123    /// If the entry at `path` is a symlink, no action is performed.
124    fn set_mode<P: openat::AsPath>(&self, path: P, mode: libc::mode_t) -> io::Result<()>;
125
126    /// Copy a regular file.  The semantics here are intended to match `std::fs::copy()`.
127    /// If the target exists, it will be overwritten.  The mode bits (permissions) will match, but
128    /// owner/group will be derived from the current process.  Extended attributes are not
129    /// copied.  However, symbolic links will not be followed; instead an error is returned.
130    /// If the filesystem supports it, reflinks will be used.
131    fn copy_file<S: openat::AsPath, D: openat::AsPath>(&self, s: S, d: D) -> io::Result<()>;
132
133    /// Copy a regular file.
134    ///
135    /// This is the same as `copy_file`, but can copy to an arbitrary target directory outside
136    /// of self.
137    fn copy_file_at<S: openat::AsPath, D: openat::AsPath>(
138        &self,
139        s: S,
140        target_dir: &openat::Dir,
141        d: D,
142    ) -> io::Result<()>;
143
144    /// Create a `FileWriter` which provides a `std::io::BufWriter` and then atomically creates
145    /// the file at the destination, renaming it over an existing one if necessary.
146    fn new_file_writer(&self, mode: libc::mode_t) -> io::Result<FileWriter>;
147
148    /// Atomically create or replace the destination file, calling the provided
149    /// function to generate the contents.  Note that the contents of the
150    /// file will not be explicitly sync'd to disk; if you want to do so you
151    /// need to invoke `writer.flush()?; writer.get_ref().sync_all()` for example.
152    fn write_file_with<P: AsRef<Path>, F, T, E>(
153        &self,
154        destname: P,
155        mode: libc::mode_t,
156        gen_content_fn: F,
157    ) -> Result<T, E>
158    where
159        F: FnOnce(&mut std::io::BufWriter<std::fs::File>) -> Result<T, E>,
160        E: From<io::Error>,
161    {
162        let mut w = self.new_file_writer(mode)?;
163        gen_content_fn(&mut w.writer).and_then(|t| {
164            w.complete(destname)?;
165            Ok(t)
166        })
167    }
168
169    /// Like `write_file_with()` but explicitly synchronizes the target to disk.
170    fn write_file_with_sync<P: AsRef<Path>, F, T, E>(
171        &self,
172        destname: P,
173        mode: libc::mode_t,
174        gen_content_fn: F,
175    ) -> Result<T, E>
176    where
177        F: FnOnce(&mut std::io::BufWriter<std::fs::File>) -> Result<T, E>,
178        E: From<io::Error>,
179    {
180        let mut w = self.new_file_writer(mode)?;
181        gen_content_fn(&mut w.writer).and_then(|t| {
182            w.complete_with(destname, |f| f.sync_all())?;
183            Ok(t)
184        })
185    }
186
187    /// Atomically create or replace the destination file with
188    /// the provided contents.
189    fn write_file_contents<P: AsRef<Path>, C: AsRef<[u8]>>(
190        &self,
191        destname: P,
192        mode: libc::mode_t,
193        contents: C,
194    ) -> io::Result<()> {
195        self.write_file_with(destname, mode, |w| w.write_all(contents.as_ref()))
196    }
197}
198
199impl OpenatDirExt for openat::Dir {
200    fn open_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<fs::File>> {
201        match self.open_file(p) {
202            Ok(f) => Ok(Some(f)),
203            Err(e) => {
204                if e.kind() == io::ErrorKind::NotFound {
205                    Ok(None)
206                } else {
207                    Err(e)
208                }
209            }
210        }
211    }
212
213    fn read_to_string<P: openat::AsPath>(&self, p: P) -> io::Result<String> {
214        impl_read_to_string(self.open_file(p)?)
215    }
216
217    fn read_to_string_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<String>> {
218        if let Some(f) = self.open_file_optional(p)? {
219            Ok(Some(impl_read_to_string(f)?))
220        } else {
221            Ok(None)
222        }
223    }
224
225    fn remove_file_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
226        impl_remove_file_optional(self, p)
227    }
228
229    fn remove_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
230        match self.remove_dir(p) {
231            Ok(_) => Ok(true),
232            Err(e) => {
233                if e.kind() == io::ErrorKind::NotFound {
234                    Ok(false)
235                } else {
236                    Err(e)
237                }
238            }
239        }
240    }
241
242    fn metadata_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Metadata>> {
243        match self.metadata(p) {
244            Ok(d) => Ok(Some(d)),
245            Err(e) => {
246                if e.kind() == io::ErrorKind::NotFound {
247                    Ok(None)
248                } else {
249                    Err(e)
250                }
251            }
252        }
253    }
254
255    fn sub_dir_optional<P: openat::AsPath>(&self, p: P) -> io::Result<Option<openat::Dir>> {
256        match self.sub_dir(p) {
257            Ok(d) => Ok(Some(d)),
258            Err(e) => {
259                if e.kind() == io::ErrorKind::NotFound {
260                    Ok(None)
261                } else {
262                    Err(e)
263                }
264            }
265        }
266    }
267
268    fn get_file_type(&self, e: &openat::Entry) -> io::Result<openat::SimpleType> {
269        if let Some(ftype) = e.simple_type() {
270            Ok(ftype)
271        } else {
272            Ok(self.metadata(e.file_name())?.simple_type())
273        }
274    }
275
276    fn exists<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
277        match self.metadata(p) {
278            Ok(_) => Ok(true),
279            Err(e) => {
280                if e.kind() == io::ErrorKind::NotFound {
281                    Ok(false)
282                } else {
283                    Err(e)
284                }
285            }
286        }
287    }
288
289    fn ensure_dir<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
290        match self.create_dir(p, mode) {
291            Ok(_) => Ok(()),
292            Err(e) => {
293                if e.kind() == io::ErrorKind::AlreadyExists {
294                    Ok(())
295                } else {
296                    Err(e)
297                }
298            }
299        }
300    }
301
302    fn ensure_dir_all<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
303        let p = to_cstr(p)?;
304        let p = p.as_ref();
305        // Convert to a `Path` basically just so that we can call `.parent()` on it.
306        let p = Path::new(OsStr::from_bytes(p.to_bytes()));
307        // Our first system call here is a direct `mkdirat()` - if that succeeds or
308        // we get EEXIST then we're done.
309        match self.create_dir(p, mode) {
310            Ok(_) => {}
311            Err(e) => match e.kind() {
312                io::ErrorKind::AlreadyExists => {}
313                // Otherwise, the expensive path
314                io::ErrorKind::NotFound => impl_ensure_dir_all(self, p, mode)?,
315                _ => return Err(e),
316            },
317        }
318        Ok(())
319    }
320
321    fn copy_file<S: openat::AsPath, D: openat::AsPath>(&self, s: S, d: D) -> io::Result<()> {
322        let src = self.open_file(s)?;
323        impl_copy_regfile(&src, self, d)
324    }
325
326    fn copy_file_at<S: openat::AsPath, D: openat::AsPath>(
327        &self,
328        s: S,
329        target_dir: &openat::Dir,
330        d: D,
331    ) -> io::Result<()> {
332        let src = self.open_file(s)?;
333        impl_copy_regfile(&src, target_dir, d)
334    }
335
336    fn remove_all<P: openat::AsPath>(&self, p: P) -> io::Result<bool> {
337        impl_remove_all(self, p)
338    }
339
340    #[allow(unsafe_code)]
341    fn syncfs(&self) -> io::Result<()> {
342        // syncfs(2) does not work with `O_PATH` FDs, so `self` cannot
343        // be directly used. Thus we have to materialize a FD for the
344        // directory first.
345        let dirfd = self.open_file(".")?;
346        let ret = unsafe { libc::syncfs(dirfd.as_raw_fd()) };
347        if ret == 0 {
348            Ok(())
349        } else {
350            Err(std::io::Error::last_os_error())
351        }
352    }
353
354    // NOTE(lucab): this isn't strictly an atomic operation, because
355    // unfortunately `renameat` overloads `ENOENT` for multiple error cases.
356    fn local_rename_optional<P: AsRef<Path>, R: AsRef<Path>>(
357        &self,
358        oldpath: P,
359        newpath: R,
360    ) -> io::Result<bool> {
361        if self.exists(oldpath.as_ref())? {
362            self.local_rename(oldpath.as_ref(), newpath.as_ref())
363                .and(Ok(true))
364        } else {
365            Ok(false)
366        }
367    }
368
369    fn update_timestamps<P: openat::AsPath>(&self, p: P) -> io::Result<()> {
370        use nix::sys::stat::{utimensat, UtimensatFlags};
371        use nix::sys::time::TimeSpec;
372
373        let path = p
374            .to_path()
375            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
376        let now = TimeSpec::from(libc::timespec {
377            tv_nsec: libc::UTIME_NOW,
378            tv_sec: 0,
379        });
380        retry_eintr!(utimensat(
381            Some(self.as_raw_fd()),
382            path.as_ref(),
383            &now,
384            &now,
385            UtimensatFlags::NoFollowSymlink,
386        )
387        .map_err(map_nix_error))
388    }
389
390    fn set_mode<P: openat::AsPath>(&self, p: P, mode: libc::mode_t) -> io::Result<()> {
391        use nix::sys::stat::{fchmodat, FchmodatFlags, Mode};
392        use openat::SimpleType;
393
394        let path = p
395            .to_path()
396            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
397
398        {
399            // NOTE(lucab): `AT_SYMLINK_NOFOLLOW` used to short-circuit to `ENOTSUP`
400            // in older glibc versions, so we don't use it. Instead we try to detect
401            // any symlink, and skip it.
402            let entry_meta = self.metadata(path.as_ref())?;
403            if entry_meta.simple_type() == SimpleType::Symlink {
404                return Ok(());
405            };
406        }
407
408        let perms = Mode::from_bits_truncate(mode);
409        fchmodat(
410            Some(self.as_raw_fd()),
411            path.as_ref(),
412            perms,
413            FchmodatFlags::FollowSymlink,
414        )
415        .map_err(map_nix_error)?;
416
417        Ok(())
418    }
419
420    fn new_file_writer(&self, mode: libc::mode_t) -> io::Result<FileWriter> {
421        let (tmpf, name) = if let Ok(tmpf) = self.new_unnamed_file(mode) {
422            (tmpf, None)
423        } else {
424            // FIXME allow this to be configurable
425            let (tmpf, name) = tempfile_in(self, ".tmp", ".tmp", mode)?;
426            (tmpf, Some(name))
427        };
428        Ok(FileWriter::new(self, tmpf, name))
429    }
430}
431
432fn impl_read_to_string(mut f: File) -> io::Result<String> {
433    let mut buf = String::new();
434    let _ = f.read_to_string(&mut buf)?;
435    Ok(buf)
436}
437
438fn map_nix_error(e: nix::Error) -> io::Error {
439    io::Error::from_raw_os_error(e as i32)
440}
441
442#[allow(deprecated)]
443fn copy_regfile_inner(
444    src: &File,
445    srcmeta: &std::fs::Metadata,
446    dest: &mut FileWriter,
447) -> io::Result<()> {
448    // Go directly to the raw file so we can reflink
449    let destf = dest.writer.get_mut();
450    let _ = src.copy_to(destf)?;
451    let nixmode = nix::sys::stat::Mode::from_bits_truncate(srcmeta.permissions().mode());
452    nix::sys::stat::fchmod(destf.as_raw_fd(), nixmode).map_err(map_nix_error)?;
453    Ok(())
454}
455
456fn impl_copy_regfile<D: openat::AsPath>(
457    src: &File,
458    target_dir: &openat::Dir,
459    d: D,
460) -> io::Result<()> {
461    let d = to_cstr(d)?;
462    let d = OsStr::from_bytes(d.as_ref().to_bytes());
463    let meta = src.metadata()?;
464    // Start in mode 0600, we will replace with the actual bits after writing
465    let mut w = target_dir.new_file_writer(0o600)?;
466    copy_regfile_inner(src, &meta, &mut w).and_then(|t| {
467        w.complete(d)?;
468        Ok(t)
469    })
470}
471
472fn impl_remove_file_optional<P: openat::AsPath>(d: &openat::Dir, path: P) -> io::Result<bool> {
473    match d.remove_file(path) {
474        Ok(_) => Ok(true),
475        Err(e) => {
476            if e.kind() == io::ErrorKind::NotFound {
477                Ok(false)
478            } else {
479                Err(e)
480            }
481        }
482    }
483}
484
485pub(crate) fn random_name(rng: &mut rand::rngs::ThreadRng, prefix: &str, suffix: &str) -> String {
486    let mut tmpname = prefix.to_string();
487    for _ in 0..8 {
488        tmpname.push(rng.sample(rand::distributions::Alphanumeric).into());
489    }
490    tmpname.push_str(suffix);
491    tmpname
492}
493
494pub(crate) fn tempfile_in(
495    d: &openat::Dir,
496    prefix: &str,
497    suffix: &str,
498    mode: libc::mode_t,
499) -> io::Result<(fs::File, String)> {
500    for _ in 0..TEMPFILE_ATTEMPTS {
501        let tmpname = random_name(&mut rand::thread_rng(), prefix, suffix);
502        match d.new_file(tmpname.as_str(), mode) {
503            Ok(f) => return Ok((f, tmpname)),
504            Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => continue,
505            Err(e) => return Err(e),
506        }
507    }
508    Err(io::Error::new(
509        io::ErrorKind::AlreadyExists,
510        format!(
511            "Exhausted {} attempts to create temporary file",
512            TEMPFILE_ATTEMPTS
513        ),
514    ))
515}
516
517/// Walk up the path components, creating each directory in turn.  This is
518/// pessimistic, assuming no components exist.  But we already handled the
519/// optimal case where all components exist above.
520pub(crate) fn impl_ensure_dir_all(d: &openat::Dir, p: &Path, mode: libc::mode_t) -> io::Result<()> {
521    if let Some(parent) = p.parent() {
522        if !parent.as_os_str().is_empty() {
523            impl_ensure_dir_all(d, parent, mode)?;
524        }
525    }
526    d.ensure_dir(p, mode)?;
527    Ok(())
528}
529
530pub(crate) fn remove_children(d: &openat::Dir, iter: openat::DirIter) -> io::Result<()> {
531    for entry in iter {
532        let entry = entry?;
533        match d.get_file_type(&entry)? {
534            openat::SimpleType::Dir => {
535                let subd = d.sub_dir(&entry)?;
536                remove_children(&subd, subd.list_dir(".")?)?;
537                let _ = d.remove_dir_optional(&entry)?;
538            }
539            _ => {
540                let _ = d.remove_file_optional(entry.file_name())?;
541            }
542        }
543    }
544    Ok(())
545}
546
547fn impl_remove_all<P: openat::AsPath>(d: &openat::Dir, p: P) -> io::Result<bool> {
548    let cp = to_cstr(p)?;
549    let cp = cp.as_ref();
550    match impl_remove_file_optional(d, cp) {
551        Ok(b) => Ok(b),
552        Err(e) => {
553            if let Some(ecode) = e.raw_os_error() {
554                match ecode {
555                    libc::ENOENT => Ok(false),
556                    libc::EISDIR => {
557                        let iter = d.list_dir(cp)?;
558                        let subd = d.sub_dir(cp)?;
559                        remove_children(&subd, iter)?;
560                        d.remove_dir(cp)?;
561                        Ok(true)
562                    }
563                    _ => Err(e),
564                }
565            } else {
566                unreachable!("Unexpected non-OS error from openat::sub_dir: {}", e)
567            }
568        }
569    }
570}
571
572/// A wrapper for atomically replacing a file.  The primary field
573/// to access here is the `writer`.  You can also configure the
574/// temporary prefix and suffix used for the temporary file before
575/// it is moved over the final destination.
576///
577/// Call `complete()` to `rename()` the file into place.
578pub struct FileWriter<'a> {
579    /// Write to the destination file.
580    pub writer: std::io::BufWriter<std::fs::File>,
581    /// This string will be used as a prefix for the temporary file
582    pub tmp_prefix: String,
583    /// This string will be used as a suffix for the temporary file
584    pub tmp_suffix: String,
585
586    /// The target directory
587    dir: &'a openat::Dir,
588    /// Our temporary file name
589    tempname: Option<String>,
590}
591
592impl<'a> FileWriter<'a> {
593    fn new(dir: &'a openat::Dir, f: std::fs::File, tempname: Option<String>) -> Self {
594        Self {
595            writer: std::io::BufWriter::new(f),
596            tempname,
597            tmp_prefix: ".tmp.".to_string(),
598            tmp_suffix: ".tmp".to_string(),
599            dir,
600        }
601    }
602
603    fn linkat(
604        dir: &openat::Dir,
605        fd: fs::File,
606        rng: &mut rand::rngs::ThreadRng,
607        prefix: &str,
608        suffix: &str,
609    ) -> io::Result<String> {
610        // Unfortunately there's no linkat(LINKAT_REPLACE) yet, so we need
611        // to generate a tempfile as the penultimate step.
612        for _ in 0..TEMPFILE_ATTEMPTS {
613            let tmpname = random_name(rng, prefix, suffix);
614            match dir.link_file_at(&fd, tmpname.as_str()) {
615                Ok(()) => {
616                    return Ok(tmpname);
617                }
618                Err(e) => {
619                    if e.kind() == io::ErrorKind::AlreadyExists {
620                        continue;
621                    } else {
622                        return Err(e);
623                    }
624                }
625            }
626        }
627        Err(io::Error::new(
628            io::ErrorKind::AlreadyExists,
629            format!(
630                "Exhausted {} attempts to create temporary file",
631                TEMPFILE_ATTEMPTS
632            ),
633        ))
634    }
635
636    /// Flush any outstanding buffered data and rename the temporary
637    /// file into place.  The provided closure will be invoked
638    /// on the real underlying file descriptor before it is
639    /// renamed into place.  You can use this to change file attributes.
640    /// For example, you can change the mode, extended attributes, or invoke
641    /// `fchmod()` to change ownership, etc.
642    pub fn complete_with<P: AsRef<Path>, F>(self, dest: P, f: F) -> io::Result<()>
643    where
644        F: Fn(&fs::File) -> io::Result<()>,
645    {
646        let dest = dest.as_ref();
647        let dir = self.dir;
648        let prefix = self.tmp_prefix;
649        let suffix = self.tmp_suffix;
650        let fd = self.writer.into_inner()?;
651        f(&fd)?;
652        let mut rng = rand::thread_rng();
653        // If we already have a temporary file name (no O_TMPFILE) then
654        // use it.  Otherwise since we're done writing, try to link it into place.
655        let tmpname = if let Some(t) = self.tempname {
656            t
657        } else {
658            Self::linkat(&dir, fd, &mut rng, prefix.as_str(), suffix.as_str())?
659        };
660        let tmpname = tmpname.as_str();
661        // Then rename it into place.
662        match self.dir.local_rename(tmpname, dest) {
663            Ok(()) => Ok(()),
664            Err(e) => {
665                // If we failed, clean up
666                let _ = self.dir.remove_file(tmpname);
667                Err(e)
668            }
669        }
670    }
671
672    /// Flush any outstanding buffered data and rename the temporary
673    /// file into place.
674    pub fn complete<P: AsRef<Path>>(self, dest: P) -> io::Result<()> {
675        self.complete_with(dest, |_f| Ok(()))
676    }
677}
678
679// Our methods take &self, not &mut self matching the other raw
680// file methods.  We can't use io::copy because it expects mutable
681// references, so just reimplement it here.
682pub(crate) fn fallback_file_copy(src: &File, dest: &File) -> io::Result<u64> {
683    let mut off: u64 = 0;
684    let mut buf = [0u8; 8192];
685    loop {
686        let n = src.read_at(&mut buf, off)?;
687        if n == 0 {
688            return Ok(off);
689        }
690        dest.write_all_at(&buf[0..n], off)?;
691        off += n as u64;
692    }
693}
694
695/// Helper functions for std::fs::File
696pub trait FileExt {
697    /// Copy the entire contents of `self` to `to`.  This uses operating system
698    /// specific fast paths if available.
699    ///
700    #[deprecated = "Use std::io::copy instead"]
701    fn copy_to(&self, to: &File) -> io::Result<u64>;
702
703    /// Update timestamps (both access and modification) to the current time.
704    fn update_timestamps(&self) -> io::Result<()>;
705
706    /// Read the exact number of bytes required to fill `buf` starting from `position`,
707    /// without affecting file offset.
708    fn pread_exact(&self, buf: &mut [u8], position: usize) -> io::Result<()>;
709}
710
711impl FileExt for File {
712    #[cfg(not(any(target_os = "linux", target_os = "android")))]
713    fn copy_to(&self, to: &File) -> io::Result<u64> {
714        fallback_file_copy(self, to)
715    }
716
717    // Derived from src/libstd/sys/unix/fs.rs in Rust
718    #[cfg(any(target_os = "linux", target_os = "android"))]
719    fn copy_to(&self, to: &File) -> io::Result<u64> {
720        use nix::errno::Errno;
721        use nix::fcntl::copy_file_range;
722        use std::sync::atomic::{AtomicBool, Ordering};
723
724        // Kernel prior to 4.5 don't have copy_file_range
725        // We store the availability in a global to avoid unnecessary syscalls
726        static HAS_COPY_FILE_RANGE: AtomicBool = AtomicBool::new(true);
727
728        let len = self.metadata()?.len();
729
730        let has_copy_file_range = HAS_COPY_FILE_RANGE.load(Ordering::Relaxed);
731        let mut written = 0u64;
732        while written < len {
733            let copy_result = if has_copy_file_range {
734                let bytes_to_copy = std::cmp::min(len - written, usize::MAX as u64) as usize;
735                // We actually don't have to adjust the offsets,
736                // because copy_file_range adjusts the file offset automatically
737                let copy_result =
738                    copy_file_range(self.as_raw_fd(), None, to.as_raw_fd(), None, bytes_to_copy);
739                if let Err(ref copy_err) = copy_result {
740                    match copy_err {
741                        Errno::ENOSYS | Errno::EPERM => {
742                            HAS_COPY_FILE_RANGE.store(false, Ordering::Relaxed);
743                        }
744                        _ => {}
745                    }
746                }
747                copy_result
748            } else {
749                Err(Errno::ENOSYS)
750            };
751            match copy_result {
752                Ok(ret) => written += ret as u64,
753                Err(err) => {
754                    match err {
755                        os_err
756                            if os_err == Errno::ENOSYS
757                                || os_err == Errno::EXDEV
758                                || os_err == Errno::EINVAL
759                                || os_err == Errno::EPERM =>
760                        {
761                            // Try fallback io::copy if either:
762                            // - Kernel version is < 4.5 (ENOSYS)
763                            // - Files are mounted on different fs (EXDEV)
764                            // - copy_file_range is disallowed, for example by seccomp (EPERM)
765                            // - copy_file_range cannot be used with pipes or device nodes (EINVAL)
766                            assert_eq!(written, 0);
767                            return fallback_file_copy(self, to);
768                        }
769                        os_err => return Err(map_nix_error(os_err)),
770                    }
771                }
772            }
773        }
774        Ok(written)
775    }
776
777    fn update_timestamps(&self) -> io::Result<()> {
778        use nix::sys::{stat::futimens, time::TimeSpec};
779
780        let now = TimeSpec::from(libc::timespec {
781            tv_nsec: libc::UTIME_NOW,
782            tv_sec: 0,
783        });
784        retry_eintr!(futimens(self.as_raw_fd(), &now, &now,).map_err(map_nix_error))
785    }
786
787    fn pread_exact(&self, buf: &mut [u8], start_pos: usize) -> io::Result<()> {
788        use nix::sys::uio::pread;
789
790        if buf.is_empty() {
791            return Err(io::Error::new(
792                io::ErrorKind::InvalidInput,
793                "zero-sized buffer in input",
794            ));
795        }
796
797        let mut total_bytes_read = 0;
798        while total_bytes_read < buf.len() {
799            let remaining_buf = &mut buf[total_bytes_read..];
800            let cur_offset = start_pos.saturating_add(total_bytes_read);
801            let bytes_read =
802                retry_eintr!(
803                    pread(self.as_raw_fd(), remaining_buf, cur_offset as libc::off_t)
804                        .map_err(map_nix_error)
805                )?;
806            total_bytes_read += bytes_read;
807            if bytes_read == 0 {
808                break;
809            }
810        }
811
812        if total_bytes_read < buf.len() {
813            return Err(io::Error::new(
814                io::ErrorKind::UnexpectedEof,
815                format!("pread reached EOF after {} bytes", total_bytes_read),
816            ));
817        }
818
819        Ok(())
820    }
821}
822
823fn to_cstr<P: openat::AsPath>(path: P) -> io::Result<P::Buffer> {
824    path.to_path()
825        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "nul byte in file name"))
826}
827
828#[cfg(test)]
829mod tests {
830    use super::*;
831    use std::path::{Path, PathBuf};
832    use std::time::Duration;
833    use std::{error, result, thread};
834    use tempfile;
835
836    type Result<T> = result::Result<T, Box<dyn error::Error>>;
837
838    #[test]
839    fn open_file_optional() -> Result<()> {
840        let td = tempfile::tempdir()?;
841        let d = openat::Dir::open(td.path())?;
842        assert!(d.open_file_optional("foo")?.is_none());
843        d.write_file("foo", 0o644)?.sync_all()?;
844        assert!(d.open_file_optional("foo")?.is_some());
845        Ok(())
846    }
847
848    #[test]
849    fn read_to_string() -> Result<()> {
850        let td = tempfile::tempdir()?;
851        let d = openat::Dir::open(td.path())?;
852        assert!(d.read_to_string("foo").is_err());
853        d.write_file_contents("foo", 0o644, "bar")?;
854        assert_eq!(d.read_to_string("foo")?, "bar");
855        Ok(())
856    }
857
858    #[test]
859    fn read_to_string_optional() -> Result<()> {
860        let td = tempfile::tempdir()?;
861        let d = openat::Dir::open(td.path())?;
862        assert!(d.read_to_string_optional("foo")?.is_none());
863        d.write_file_contents("foo", 0o644, "bar")?;
864        assert!(d.read_to_string_optional("foo")?.is_some());
865        assert_eq!(d.read_to_string_optional("foo")?.unwrap(), "bar");
866        Ok(())
867    }
868
869    #[test]
870    fn remove_file_optional() -> Result<()> {
871        let td = tempfile::tempdir()?;
872        let d = openat::Dir::open(td.path())?;
873        d.write_file("foo", 0o644)?.sync_all()?;
874        assert!(d.open_file_optional("foo")?.is_some());
875        let removed = d.remove_file_optional("foo")?;
876        assert!(removed);
877        assert!(d.open_file_optional("foo")?.is_none());
878        Ok(())
879    }
880
881    #[test]
882    fn metadata_optional() -> Result<()> {
883        let td = tempfile::tempdir()?;
884        let d = openat::Dir::open(td.path())?;
885        assert!(d.metadata_optional("foo")?.is_none());
886        d.write_file("foo", 0o644)?.sync_all()?;
887        assert!(d.metadata_optional("foo")?.is_some());
888        Ok(())
889    }
890
891    #[test]
892    fn get_file_type() -> Result<()> {
893        let td = tempfile::tempdir()?;
894        let d = openat::Dir::open(td.path())?;
895        d.write_file("foo", 0o644)?.sync_all()?;
896        for x in d.list_dir(".")? {
897            let x = x?;
898            assert_eq!("foo", x.file_name());
899            let t = d.get_file_type(&x)?;
900            assert_eq!(openat::SimpleType::File, t);
901        }
902        Ok(())
903    }
904
905    #[test]
906    fn ensure_dir_all() -> Result<()> {
907        let td = tempfile::tempdir()?;
908        let d = openat::Dir::open(td.path())?;
909        let mode = 0o755;
910        let p = Path::new("foo/bar/baz");
911        d.ensure_dir_all(p, mode)?;
912        assert_eq!(d.metadata(p)?.stat().st_mode & !libc::S_IFMT, mode);
913        d.ensure_dir_all(p, mode)?;
914        d.ensure_dir_all("foo/bar", mode)?;
915        d.ensure_dir_all("foo", mode)?;
916        d.ensure_dir_all("bar", 0o700)?;
917        assert_eq!(d.metadata("bar")?.stat().st_mode & !libc::S_IFMT, 0o700);
918        Ok(())
919    }
920
921    #[test]
922    fn test_local_rename() {
923        let td = tempfile::tempdir().unwrap();
924        let d = openat::Dir::open(td.path()).unwrap();
925
926        {
927            d.ensure_dir_all("src/foo", 0o755).unwrap();
928            let renamed = d.local_rename_optional("src", "dst").unwrap();
929            assert_eq!(renamed, true);
930            assert_eq!(d.exists("src").unwrap(), false);
931            assert_eq!(d.exists("dst/foo").unwrap(), true);
932            let noent = d.local_rename_optional("src", "dst").unwrap();
933            assert_eq!(noent, false);
934            assert_eq!(d.remove_all("dst").unwrap(), true);
935        }
936        {
937            let noent = d.local_rename_optional("missing", "dst").unwrap();
938            assert_eq!(noent, false);
939        }
940        {
941            d.ensure_dir_all("src/foo", 0o755).unwrap();
942            let renamed = d.local_rename_optional("src", "dst").unwrap();
943            assert_eq!(renamed, true);
944            assert_eq!(d.exists("dst/foo").unwrap(), true);
945            d.ensure_dir_all("src", 0o755).unwrap();
946            let _ = d.local_rename_optional("src", "dst").unwrap_err();
947            assert_eq!(d.exists("dst/foo").unwrap(), true);
948            assert_eq!(d.remove_all("dst").unwrap(), true);
949        }
950    }
951
952    #[test]
953    fn test_syncfs() {
954        let td = tempfile::tempdir().unwrap();
955        let d = openat::Dir::open(td.path()).unwrap();
956        d.ensure_dir_all("foo/bar", 0o755).unwrap();
957        d.syncfs().unwrap();
958        assert_eq!(d.exists("foo/bar").unwrap(), true);
959    }
960
961    #[test]
962    fn test_update_timestamps() {
963        // File timestamps can not be updated faster than kernel ticking granularity,
964        // so this test has to artificially sleep through several timer interrupts.
965        const TICKING_PAUSE: Duration = Duration::from_millis(100);
966
967        let td = tempfile::tempdir().unwrap();
968        let d = openat::Dir::open(td.path()).unwrap();
969
970        {
971            d.ensure_dir("foo", 0o755).unwrap();
972            let before = d.metadata("foo").unwrap();
973            thread::sleep(TICKING_PAUSE);
974            d.update_timestamps("foo").unwrap();
975            let after = d.metadata("foo").unwrap();
976            if before.stat().st_mtime == after.stat().st_mtime {
977                assert_ne!(before.stat().st_mtime_nsec, after.stat().st_mtime_nsec);
978            }
979        }
980        {
981            use nix::sys::stat::fstat;
982
983            let bar = d.update_file("bar", 0o644).unwrap();
984            let before = fstat(bar.as_raw_fd()).unwrap();
985            thread::sleep(TICKING_PAUSE);
986            bar.update_timestamps().unwrap();
987            let after = fstat(bar.as_raw_fd()).unwrap();
988            if before.st_mtime == after.st_mtime {
989                assert_ne!(before.st_mtime_nsec, after.st_mtime_nsec);
990            }
991        }
992    }
993
994    #[test]
995    fn test_fchmodat() {
996        let td = tempfile::tempdir().unwrap();
997        let d = openat::Dir::open(td.path()).unwrap();
998        d.ensure_dir("foo", 0o777).unwrap();
999        d.set_mode("foo", 0o750).unwrap();
1000        assert_eq!(
1001            d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1002            0o750
1003        );
1004        d.set_mode("foo", 0o700).unwrap();
1005        assert_eq!(
1006            d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1007            0o700
1008        );
1009
1010        d.symlink("bar", "foo").unwrap();
1011        d.set_mode("bar", 0o000).unwrap();
1012        assert_ne!(
1013            d.metadata("bar").unwrap().stat().st_mode & !libc::S_IFMT,
1014            0o000
1015        );
1016        assert_ne!(
1017            d.metadata("foo").unwrap().stat().st_mode & !libc::S_IFMT,
1018            0o000
1019        );
1020    }
1021
1022    fn find_test_file(tempdir: &Path) -> Result<PathBuf> {
1023        for p in ["/proc/self/exe", "/usr/bin/bash"].iter() {
1024            let p = Path::new(p);
1025            if p.exists() {
1026                return Ok(p.into());
1027            }
1028        }
1029        let fallback = tempdir.join("testfile-fallback");
1030        std::fs::write(&fallback, "some test data")?;
1031        Ok(fallback)
1032    }
1033
1034    #[test]
1035    fn copy_fallback() -> Result<()> {
1036        use std::io::Read;
1037        let td = tempfile::tempdir()?;
1038        let src_p = find_test_file(td.path())?;
1039        let dest_p = td.path().join("bash");
1040        {
1041            let src = File::open(&src_p)?;
1042            let dest = File::create(&dest_p)?;
1043            let _ = fallback_file_copy(&src, &dest)?;
1044        }
1045        let mut src = File::open(&src_p)?;
1046        let mut srcbuf = Vec::new();
1047        let _ = src.read_to_end(&mut srcbuf)?;
1048        let mut destbuf = Vec::new();
1049        let mut dest = File::open(&dest_p)?;
1050        let _ = dest.read_to_end(&mut destbuf)?;
1051        assert_eq!(srcbuf.len(), destbuf.len());
1052        assert_eq!(&srcbuf, &destbuf);
1053        Ok(())
1054    }
1055
1056    #[test]
1057    fn copy_file_at() {
1058        let src_td = tempfile::tempdir().unwrap();
1059        let src_dir = openat::Dir::open(src_td.path()).unwrap();
1060        src_dir
1061            .write_file_contents("foo", 0o644, "test content")
1062            .unwrap();
1063
1064        let dst_td = tempfile::tempdir().unwrap();
1065        let dst_dir = openat::Dir::open(dst_td.path()).unwrap();
1066
1067        assert_eq!(dst_dir.exists("bar").unwrap(), false);
1068        src_dir.copy_file_at("foo", &dst_dir, "bar").unwrap();
1069        assert_eq!(dst_dir.exists("bar").unwrap(), true);
1070
1071        let srcbuf = {
1072            let mut src = src_dir.open_file("foo").unwrap();
1073            let mut srcbuf = Vec::new();
1074            let _ = src.read_to_end(&mut srcbuf).unwrap();
1075            srcbuf
1076        };
1077        let destbuf = {
1078            let mut destbuf = Vec::new();
1079            let mut dest = dst_dir.open_file("bar").unwrap();
1080            let _ = dest.read_to_end(&mut destbuf).unwrap();
1081            destbuf
1082        };
1083        assert_eq!(&srcbuf, b"test content");
1084        assert_eq!(&srcbuf, &destbuf);
1085    }
1086
1087    #[test]
1088    fn test_pread_exact() {
1089        let td = tempfile::tempdir().unwrap();
1090        let d = openat::Dir::open(td.path()).unwrap();
1091        static TESTINPUT: &str = "test1 test2 test3";
1092        d.write_file_contents("foo", 0o700, TESTINPUT).unwrap();
1093        let mut testfile = d.open_file("foo").unwrap();
1094        {
1095            let mut buf = [0; 0];
1096            let _ = testfile.pread_exact(&mut buf, 0).unwrap_err();
1097        }
1098        {
1099            let mut buf = [0; 18];
1100            let _ = testfile.pread_exact(&mut buf, 0).unwrap_err();
1101        }
1102        {
1103            let mut buf = [0; 1];
1104            let _ = testfile.pread_exact(&mut buf, 2000).unwrap_err();
1105        }
1106        {
1107            let mut buf1 = [0; 5];
1108            let mut buf2 = [0; 5];
1109            let _ = testfile.pread_exact(&mut buf1, 6).unwrap();
1110            let _ = testfile.pread_exact(&mut buf2, 6).unwrap();
1111            assert_eq!(buf1, "test2".as_bytes());
1112            assert_eq!(buf1, buf2);
1113
1114            let mut str_buf = String::new();
1115            let _ = testfile.read_to_string(&mut str_buf).unwrap();
1116            assert_eq!(str_buf, TESTINPUT);
1117        }
1118    }
1119}