#
cargo_util/
paths.rs

    1//! Various utilities for working with files and paths.
    2
    3use anyhow::{Context, Result};
    4use filetime::FileTime;
    5use std::env;
    6use std::ffi::{OsStr, OsString};
    7use std::fs::{self, File, Metadata, OpenOptions};
    8use std::io;
    9use std::io::prelude::*;
    10use std::iter;
    11use std::path::{Component, Path, PathBuf};
    12use tempfile::Builder as TempFileBuilder;
    13
    14/// Joins paths into a string suitable for the `PATH` environment variable.
    15///
    16/// This is equivalent to [`std::env::join_paths`], but includes a more
    17/// detailed error message. The given `env` argument is the name of the
    18/// environment variable this is will be used for, which is included in the
    19/// error message.
    20pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
    21    env::join_paths(paths.iter()).with_context(|| {
    22        let mut message = format!(
    23            "failed to join paths from `${env}` together\n\n\
    24             Check if any of path segments listed below contain an \
    25             unterminated quote character or path separator:"
    26        );
    27        for path in paths {
    28            use std::fmt::Write;
    29            write!(&mut message, "\n    {:?}", Path::new(path)).unwrap();
    30        }
    31
    32        message
    33    })
    34}
    35
    36/// Returns the name of the environment variable used for searching for
    37/// dynamic libraries.
    38pub fn dylib_path_envvar() -> &'static str {
    39    if cfg!(windows) {
    40        "PATH"
    41    } else if cfg!(target_os = "macos") {
    42        // When loading and linking a dynamic library or bundle, dlopen
    43        // searches in LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PWD, and
    44        // DYLD_FALLBACK_LIBRARY_PATH.
    45        // In the Mach-O format, a dynamic library has an "install path."
    46        // Clients linking against the library record this path, and the
    47        // dynamic linker, dyld, uses it to locate the library.
    48        // dyld searches DYLD_LIBRARY_PATH *before* the install path.
    49        // dyld searches DYLD_FALLBACK_LIBRARY_PATH only if it cannot
    50        // find the library in the install path.
    51        // Setting DYLD_LIBRARY_PATH can easily have unintended
    52        // consequences.
    53        //
    54        // Also, DYLD_LIBRARY_PATH appears to have significant performance
    55        // penalty starting in 10.13. Cargo's testsuite ran more than twice as
    56        // slow with it on CI.
    57        "DYLD_FALLBACK_LIBRARY_PATH"
    58    } else if cfg!(target_os = "aix") {
    59        "LIBPATH"
    60    } else {
    61        "LD_LIBRARY_PATH"
    62    }
    63}
    64
    65/// Returns a list of directories that are searched for dynamic libraries.
    66///
    67/// Note that some operating systems will have defaults if this is empty that
    68/// will need to be dealt with.
    69pub fn dylib_path() -> Vec<PathBuf> {
    70    match env::var_os(dylib_path_envvar()) {
    71        Some(var) => env::split_paths(&var).collect(),
    72        None => Vec::new(),
    73    }
    74}
    75
    76/// Normalize a path, removing things like `.` and `..`.
    77///
    78/// CAUTION: This does not resolve symlinks (unlike
    79/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
    80/// behavior at times. This should be used carefully. Unfortunately,
    81/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
    82/// fail, or on Windows returns annoying device paths. This is a problem Cargo
    83/// needs to improve on.
    84pub fn normalize_path(path: &Path) -> PathBuf {
    85    let mut components = path.components().peekable();
    86    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
    87        components.next();
    88        PathBuf::from(c.as_os_str())
    89    } else {
    90        PathBuf::new()
    91    };
    92
    93    for component in components {
    94        match component {
    95            Component::Prefix(..) => unreachable!(),
    96            Component::RootDir => {
    97                ret.push(Component::RootDir);
    98            }
    99            Component::CurDir => {}
    100            Component::ParentDir => {
    101                if ret.ends_with(Component::ParentDir) {
    102                    ret.push(Component::ParentDir);
    103                } else {
    104                    let popped = ret.pop();
    105                    if !popped && !ret.has_root() {
    106                        ret.push(Component::ParentDir);
    107                    }
    108                }
    109            }
    110            Component::Normal(c) => {
    111                ret.push(c);
    112            }
    113        }
    114    }
    115    ret
    116}
    117
    118/// Returns the absolute path of where the given executable is located based
    119/// on searching the `PATH` environment variable.
    120///
    121/// Returns an error if it cannot be found.
    122pub fn resolve_executable(exec: &Path) -> Result<PathBuf> {
    123    if exec.components().count() == 1 {
    124        let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?;
    125        let candidates = env::split_paths(&paths).flat_map(|path| {
    126            let candidate = path.join(&exec);
    127            let with_exe = if env::consts::EXE_EXTENSION.is_empty() {
    128                None
    129            } else {
    130                Some(candidate.with_extension(env::consts::EXE_EXTENSION))
    131            };
    132            iter::once(candidate).chain(with_exe)
    133        });
    134        for candidate in candidates {
    135            if candidate.is_file() {
    136                return Ok(candidate);
    137            }
    138        }
    139
    140        anyhow::bail!("no executable for `{}` found in PATH", exec.display())
    141    } else {
    142        Ok(exec.into())
    143    }
    144}
    145
    146/// Returns metadata for a file (follows symlinks).
    147///
    148/// Equivalent to [`std::fs::metadata`] with better error messages.
    149pub fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
    150    let path = path.as_ref();
    151    std::fs::metadata(path)
    152        .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
    153}
    154
    155/// Returns metadata for a file without following symlinks.
    156///
    157/// Equivalent to [`std::fs::metadata`] with better error messages.
    158pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
    159    let path = path.as_ref();
    160    std::fs::symlink_metadata(path)
    161        .with_context(|| format!("failed to load metadata for path `{}`", path.display()))
    162}
    163
    164/// Reads a file to a string.
    165///
    166/// Equivalent to [`std::fs::read_to_string`] with better error messages.
    167pub fn read(path: &Path) -> Result<String> {
    168    match String::from_utf8(read_bytes(path)?) {
    169        Ok(s) => Ok(s),
    170        Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()),
    171    }
    172}
    173
    174/// Reads a file into a bytes vector.
    175///
    176/// Equivalent to [`std::fs::read`] with better error messages.
    177pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
    178    fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
    179}
    180
    181/// Writes a file to disk.
    182///
    183/// Equivalent to [`std::fs::write`] with better error messages.
    184pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
    185    let path = path.as_ref();
    186    fs::write(path, contents.as_ref())
    187        .with_context(|| format!("failed to write `{}`", path.display()))
    188}
    189
    190/// Writes a file to disk atomically.
    191///
    192/// This uses `tempfile::persist` to accomplish atomic writes.
    193pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
    194    let path = path.as_ref();
    195
    196    // On unix platforms, get the permissions of the original file. Copy only the user/group/other
    197    // read/write/execute permission bits. The tempfile lib defaults to an initial mode of 0o600,
    198    // and we'll set the proper permissions after creating the file.
    199    #[cfg(unix)]
    200    let perms = path.metadata().ok().map(|meta| {
    201        use std::os::unix::fs::PermissionsExt;
    202
    203        // these constants are u16 on macOS
    204        let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
    205        let mode = meta.permissions().mode() & mask;
    206
    207        std::fs::Permissions::from_mode(mode)
    208    });
    209
    210    let mut tmp = TempFileBuilder::new()
    211        .prefix(path.file_name().unwrap())
    212        .tempfile_in(path.parent().unwrap())?;
    213    tmp.write_all(contents.as_ref())?;
    214
    215    // On unix platforms, set the permissions on the newly created file. We can use fchmod (called
    216    // by the std lib; subject to change) which ignores the umask so that the new file has the same
    217    // permissions as the old file.
    218    #[cfg(unix)]
    219    if let Some(perms) = perms {
    220        tmp.as_file().set_permissions(perms)?;
    221    }
    222
    223    tmp.persist(path)?;
    224    Ok(())
    225}
    226
    227/// Equivalent to [`write()`], but does not write anything if the file contents
    228/// are identical to the given contents.
    229pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
    230    (|| -> Result<()> {
    231        let contents = contents.as_ref();
    232        let mut f = OpenOptions::new()
    233            .read(true)
    234            .write(true)
    235            .create(true)
    236            .open(&path)?;
    237        let mut orig = Vec::new();
    238        f.read_to_end(&mut orig)?;
    239        if orig != contents {
    240            f.set_len(0)?;
    241            f.seek(io::SeekFrom::Start(0))?;
    242            f.write_all(contents)?;
    243        }
    244        Ok(())
    245    })()
    246    .with_context(|| format!("failed to write `{}`", path.as_ref().display()))?;
    247    Ok(())
    248}
    249
    250/// Equivalent to [`write()`], but appends to the end instead of replacing the
    251/// contents.
    252pub fn append(path: &Path, contents: &[u8]) -> Result<()> {
    253    (|| -> Result<()> {
    254        let mut f = OpenOptions::new()
    255            .write(true)
    256            .append(true)
    257            .create(true)
    258            .open(path)?;
    259
    260        f.write_all(contents)?;
    261        Ok(())
    262    })()
    263    .with_context(|| format!("failed to write `{}`", path.display()))?;
    264    Ok(())
    265}
    266
    267/// Creates a new file.
    268pub fn create<P: AsRef<Path>>(path: P) -> Result<File> {
    269    let path = path.as_ref();
    270    File::create(path).with_context(|| format!("failed to create file `{}`", path.display()))
    271}
    272
    273/// Opens an existing file.
    274pub fn open<P: AsRef<Path>>(path: P) -> Result<File> {
    275    let path = path.as_ref();
    276    File::open(path).with_context(|| format!("failed to open file `{}`", path.display()))
    277}
    278
    279/// Returns the last modification time of a file.
    280pub fn mtime(path: &Path) -> Result<FileTime> {
    281    let meta = metadata(path)?;
    282    Ok(FileTime::from_last_modification_time(&meta))
    283}
    284
    285/// Returns the maximum mtime of the given path, recursing into
    286/// subdirectories, and following symlinks.
    287pub fn mtime_recursive(path: &Path) -> Result<FileTime> {
    288    let meta = metadata(path)?;
    289    if !meta.is_dir() {
    290        return Ok(FileTime::from_last_modification_time(&meta));
    291    }
    292    let max_meta = walkdir::WalkDir::new(path)
    293        .follow_links(true)
    294        .into_iter()
    295        .filter_map(|e| match e {
    296            Ok(e) => Some(e),
    297            Err(e) => {
    298                // Ignore errors while walking. If Cargo can't access it, the
    299                // build script probably can't access it, either.
    300                tracing::debug!("failed to determine mtime while walking directory: {}", e);
    301                None
    302            }
    303        })
    304        .filter_map(|e| {
    305            if e.path_is_symlink() {
    306                // Use the mtime of both the symlink and its target, to
    307                // handle the case where the symlink is modified to a
    308                // different target.
    309                let sym_meta = match std::fs::symlink_metadata(e.path()) {
    310                    Ok(m) => m,
    311                    Err(err) => {
    312                        // I'm not sure when this is really possible (maybe a
    313                        // race with unlinking?). Regardless, if Cargo can't
    314                        // read it, the build script probably can't either.
    315                        tracing::debug!(
    316                            "failed to determine mtime while fetching symlink metadata of {}: {}",
    317                            e.path().display(),
    318                            err
    319                        );
    320                        return None;
    321                    }
    322                };
    323                let sym_mtime = FileTime::from_last_modification_time(&sym_meta);
    324                // Walkdir follows symlinks.
    325                match e.metadata() {
    326                    Ok(target_meta) => {
    327                        let target_mtime = FileTime::from_last_modification_time(&target_meta);
    328                        Some(sym_mtime.max(target_mtime))
    329                    }
    330                    Err(err) => {
    331                        // Can't access the symlink target. If Cargo can't
    332                        // access it, the build script probably can't access
    333                        // it either.
    334                        tracing::debug!(
    335                            "failed to determine mtime of symlink target for {}: {}",
    336                            e.path().display(),
    337                            err
    338                        );
    339                        Some(sym_mtime)
    340                    }
    341                }
    342            } else {
    343                let meta = match e.metadata() {
    344                    Ok(m) => m,
    345                    Err(err) => {
    346                        // I'm not sure when this is really possible (maybe a
    347                        // race with unlinking?). Regardless, if Cargo can't
    348                        // read it, the build script probably can't either.
    349                        tracing::debug!(
    350                            "failed to determine mtime while fetching metadata of {}: {}",
    351                            e.path().display(),
    352                            err
    353                        );
    354                        return None;
    355                    }
    356                };
    357                Some(FileTime::from_last_modification_time(&meta))
    358            }
    359        })
    360        .max()
    361        // or_else handles the case where there are no files in the directory.
    362        .unwrap_or_else(|| FileTime::from_last_modification_time(&meta));
    363    Ok(max_meta)
    364}
    365
    366/// Record the current time on the filesystem (using the filesystem's clock)
    367/// using a file at the given directory. Returns the current time.
    368pub fn set_invocation_time(path: &Path) -> Result<FileTime> {
    369    // note that if `FileTime::from_system_time(SystemTime::now());` is determined to be sufficient,
    370    // then this can be removed.
    371    let timestamp = path.join("invoked.timestamp");
    372    write(
    373        &timestamp,
    374        "This file has an mtime of when this was started.",
    375    )?;
    376    let ft = mtime(&timestamp)?;
    377    tracing::debug!("invocation time for {:?} is {}", path, ft);
    378    Ok(ft)
    379}
    380
    381/// Converts a path to UTF-8 bytes.
    382pub fn path2bytes(path: &Path) -> Result<&[u8]> {
    383    #[cfg(unix)]
    384    {
    385        use std::os::unix::prelude::*;
    386        Ok(path.as_os_str().as_bytes())
    387    }
    388    #[cfg(windows)]
    389    {
    390        match path.as_os_str().to_str() {
    391            Some(s) => Ok(s.as_bytes()),
    392            None => Err(anyhow::format_err!(
    393                "invalid non-unicode path: {}",
    394                path.display()
    395            )),
    396        }
    397    }
    398}
    399
    400/// Converts UTF-8 bytes to a path.
    401pub fn bytes2path(bytes: &[u8]) -> Result<PathBuf> {
    402    #[cfg(unix)]
    403    {
    404        use std::os::unix::prelude::*;
    405        Ok(PathBuf::from(OsStr::from_bytes(bytes)))
    406    }
    407    #[cfg(windows)]
    408    {
    409        use std::str;
    410        match str::from_utf8(bytes) {
    411            Ok(s) => Ok(PathBuf::from(s)),
    412            Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
    413        }
    414    }
    415}
    416
    417/// Returns an iterator that walks up the directory hierarchy towards the root.
    418///
    419/// Each item is a [`Path`]. It will start with the given path, finishing at
    420/// the root. If the `stop_root_at` parameter is given, it will stop at the
    421/// given path (which will be the last item).
    422pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
    423    PathAncestors::new(path, stop_root_at)
    424}
    425
    426pub struct PathAncestors<'a> {
    427    current: Option<&'a Path>,
    428    stop_at: Option<PathBuf>,
    429}
    430
    431impl<'a> PathAncestors<'a> {
    432    fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
    433        let stop_at = env::var("__CARGO_TEST_ROOT")
    434            .ok()
    435            .map(PathBuf::from)
    436            .or_else(|| stop_root_at.map(|p| p.to_path_buf()));
    437        PathAncestors {
    438            current: Some(path),
    439            //HACK: avoid reading `~/.cargo/config` when testing Cargo itself.
    440            stop_at,
    441        }
    442    }
    443}
    444
    445impl<'a> Iterator for PathAncestors<'a> {
    446    type Item = &'a Path;
    447
    448    fn next(&mut self) -> Option<&'a Path> {
    449        if let Some(path) = self.current {
    450            self.current = path.parent();
    451
    452            if let Some(ref stop_at) = self.stop_at {
    453                if path == stop_at {
    454                    self.current = None;
    455                }
    456            }
    457
    458            Some(path)
    459        } else {
    460            None
    461        }
    462    }
    463}
    464
    465/// Equivalent to [`std::fs::create_dir_all`] with better error messages.
    466pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
    467    _create_dir_all(p.as_ref())
    468}
    469
    470fn _create_dir_all(p: &Path) -> Result<()> {
    471    fs::create_dir_all(p)
    472        .with_context(|| format!("failed to create directory `{}`", p.display()))?;
    473    Ok(())
    474}
    475
    476/// Equivalent to [`std::fs::remove_dir_all`] with better error messages.
    477///
    478/// This does *not* follow symlinks.
    479pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> Result<()> {
    480    _remove_dir_all(p.as_ref()).or_else(|prev_err| {
    481        // `std::fs::remove_dir_all` is highly specialized for different platforms
    482        // and may be more reliable than a simple walk. We try the walk first in
    483        // order to report more detailed errors.
    484        fs::remove_dir_all(p.as_ref()).with_context(|| {
    485            format!(
    486                "{:?}\n\nError: failed to remove directory `{}`",
    487                prev_err,
    488                p.as_ref().display(),
    489            )
    490        })
    491    })
    492}
    493
    494fn _remove_dir_all(p: &Path) -> Result<()> {
    495    if symlink_metadata(p)?.is_symlink() {
    496        return remove_file(p);
    497    }
    498    let entries = p
    499        .read_dir()
    500        .with_context(|| format!("failed to read directory `{}`", p.display()))?;
    501    for entry in entries {
    502        let entry = entry?;
    503        let path = entry.path();
    504        if entry.file_type()?.is_dir() {
    505            remove_dir_all(&path)?;
    506        } else {
    507            remove_file(&path)?;
    508        }
    509    }
    510    remove_dir(&p)
    511}
    512
    513/// Equivalent to [`std::fs::remove_dir`] with better error messages.
    514pub fn remove_dir<P: AsRef<Path>>(p: P) -> Result<()> {
    515    _remove_dir(p.as_ref())
    516}
    517
    518fn _remove_dir(p: &Path) -> Result<()> {
    519    fs::remove_dir(p).with_context(|| format!("failed to remove directory `{}`", p.display()))?;
    520    Ok(())
    521}
    522
    523/// Equivalent to [`std::fs::remove_file`] with better error messages.
    524///
    525/// If the file is readonly, this will attempt to change the permissions to
    526/// force the file to be deleted.
    527/// On Windows, if the file is a symlink to a directory, this will attempt to remove
    528/// the symlink itself.
    529pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
    530    _remove_file(p.as_ref())
    531}
    532
    533fn _remove_file(p: &Path) -> Result<()> {
    534    // For Windows, we need to check if the file is a symlink to a directory
    535    // and remove the symlink itself by calling `remove_dir` instead of
    536    // `remove_file`.
    537    #[cfg(target_os = "windows")]
    538    {
    539        use std::os::windows::fs::FileTypeExt;
    540        let metadata = symlink_metadata(p)?;
    541        let file_type = metadata.file_type();
    542        if file_type.is_symlink_dir() {
    543            return remove_symlink_dir_with_permission_check(p);
    544        }
    545    }
    546
    547    remove_file_with_permission_check(p)
    548}
    549
    550#[cfg(target_os = "windows")]
    551fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
    552    remove_with_permission_check(fs::remove_dir, p)
    553        .with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
    554}
    555
    556fn remove_file_with_permission_check(p: &Path) -> Result<()> {
    557    remove_with_permission_check(fs::remove_file, p)
    558        .with_context(|| format!("failed to remove file `{}`", p.display()))
    559}
    560
    561fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
    562where
    563    F: Fn(P) -> io::Result<()>,
    564    P: AsRef<Path> + Clone,
    565{
    566    match remove_func(p.clone()) {
    567        Ok(()) => Ok(()),
    568        Err(e) => {
    569            if e.kind() == io::ErrorKind::PermissionDenied
    570                && set_not_readonly(p.as_ref()).unwrap_or(false)
    571            {
    572                remove_func(p)
    573            } else {
    574                Err(e)
    575            }
    576        }
    577    }
    578}
    579
    580fn set_not_readonly(p: &Path) -> io::Result<bool> {
    581    let mut perms = p.metadata()?.permissions();
    582    if !perms.readonly() {
    583        return Ok(false);
    584    }
    585    perms.set_readonly(false);
    586    fs::set_permissions(p, perms)?;
    587    Ok(true)
    588}
    589
    590/// Hardlink (file) or symlink (dir) src to dst if possible, otherwise copy it.
    591///
    592/// If the destination already exists, it is removed before linking.
    593pub fn link_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
    594    let src = src.as_ref();
    595    let dst = dst.as_ref();
    596    _link_or_copy(src, dst)
    597}
    598
    599fn _link_or_copy(src: &Path, dst: &Path) -> Result<()> {
    600    tracing::debug!("linking {} to {}", src.display(), dst.display());
    601    if same_file::is_same_file(src, dst).unwrap_or(false) {
    602        return Ok(());
    603    }
    604
    605    // NB: we can't use dst.exists(), as if dst is a broken symlink,
    606    // dst.exists() will return false. This is problematic, as we still need to
    607    // unlink dst in this case. symlink_metadata(dst).is_ok() will tell us
    608    // whether dst exists *without* following symlinks, which is what we want.
    609    if fs::symlink_metadata(dst).is_ok() {
    610        remove_file(&dst)?;
    611    }
    612
    613    let link_result = if src.is_dir() {
    614        #[cfg(unix)]
    615        use std::os::unix::fs::symlink;
    616        #[cfg(windows)]
    617        // FIXME: This should probably panic or have a copy fallback. Symlinks
    618        // are not supported in all windows environments. Currently symlinking
    619        // is only used for .dSYM directories on macos, but this shouldn't be
    620        // accidentally relied upon.
    621        use std::os::windows::fs::symlink_dir as symlink;
    622
    623        let dst_dir = dst.parent().unwrap();
    624        let src = if src.starts_with(dst_dir) {
    625            src.strip_prefix(dst_dir).unwrap()
    626        } else {
    627            src
    628        };
    629        symlink(src, dst)
    630    } else {
    631        if cfg!(target_os = "macos") {
    632            // There seems to be a race condition with APFS when hard-linking
    633            // binaries. Gatekeeper does not have signing or hash information
    634            // stored in kernel when running the process. Therefore killing it.
    635            // This problem does not appear when copying files as kernel has
    636            // time to process it. Note that: fs::copy on macos is using
    637            // CopyOnWrite (syscall fclonefileat) which should be as fast as
    638            // hardlinking. See these issues for the details:
    639            //
    640            // * https://github.com/rust-lang/cargo/issues/7821
    641            // * https://github.com/rust-lang/cargo/issues/10060
    642            fs::copy(src, dst).map_or_else(
    643                |e| {
    644                    if e.raw_os_error()
    645                        .map_or(false, |os_err| os_err == 35 /* libc::EAGAIN */)
    646                    {
    647                        tracing::info!("copy failed {e:?}. falling back to fs::hard_link");
    648
    649                        // Working around an issue copying too fast with zfs (probably related to
    650                        // https://github.com/openzfsonosx/zfs/issues/809)
    651                        // See https://github.com/rust-lang/cargo/issues/13838
    652                        fs::hard_link(src, dst)
    653                    } else {
    654                        Err(e)
    655                    }
    656                },
    657                |_| Ok(()),
    658            )
    659        } else {
    660            fs::hard_link(src, dst)
    661        }
    662    };
    663    link_result
    664        .or_else(|err| {
    665            tracing::debug!("link failed {}. falling back to fs::copy", err);
    666            fs::copy(src, dst).map(|_| ())
    667        })
    668        .with_context(|| {
    669            format!(
    670                "failed to link or copy `{}` to `{}`",
    671                src.display(),
    672                dst.display()
    673            )
    674        })?;
    675    Ok(())
    676}
    677
    678/// Copies a file from one location to another.
    679///
    680/// Equivalent to [`std::fs::copy`] with better error messages.
    681pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
    682    let from = from.as_ref();
    683    let to = to.as_ref();
    684    fs::copy(from, to)
    685        .with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))
    686}
    687
    688/// Changes the filesystem mtime (and atime if possible) for the given file.
    689///
    690/// This intentionally does not return an error, as this is sometimes not
    691/// supported on network filesystems. For the current uses in Cargo, this is a
    692/// "best effort" approach, and errors shouldn't be propagated.
    693pub fn set_file_time_no_err<P: AsRef<Path>>(path: P, time: FileTime) {
    694    let path = path.as_ref();
    695    match filetime::set_file_times(path, time, time) {
    696        Ok(()) => tracing::debug!("set file mtime {} to {}", path.display(), time),
    697        Err(e) => tracing::warn!(
    698            "could not set mtime of {} to {}: {:?}",
    699            path.display(),
    700            time,
    701            e
    702        ),
    703    }
    704}
    705
    706/// Strips `base` from `path`.
    707///
    708/// This canonicalizes both paths before stripping. This is useful if the
    709/// paths are obtained in different ways, and one or the other may or may not
    710/// have been normalized in some way.
    711pub fn strip_prefix_canonical(
    712    path: impl AsRef<Path>,
    713    base: impl AsRef<Path>,
    714) -> Result<PathBuf, std::path::StripPrefixError> {
    715    // Not all filesystems support canonicalize. Just ignore if it doesn't work.
    716    let safe_canonicalize = |path: &Path| match path.canonicalize() {
    717        Ok(p) => p,
    718        Err(e) => {
    719            tracing::warn!("cannot canonicalize {:?}: {:?}", path, e);
    720            path.to_path_buf()
    721        }
    722    };
    723    let canon_path = safe_canonicalize(path.as_ref());
    724    let canon_base = safe_canonicalize(base.as_ref());
    725    canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
    726}
    727
    728/// Creates an excluded from cache directory atomically with its parents as needed.
    729///
    730/// The atomicity only covers creating the leaf directory and exclusion from cache. Any missing
    731/// parent directories will not be created in an atomic manner.
    732///
    733/// This function is idempotent and in addition to that it won't exclude ``p`` from cache if it
    734/// already exists.
    735pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> Result<()> {
    736    let path = p.as_ref();
    737    if path.is_dir() {
    738        return Ok(());
    739    }
    740
    741    let parent = path.parent().unwrap();
    742    let base = path.file_name().unwrap();
    743    create_dir_all(parent)?;
    744    // We do this in two steps (first create a temporary directory and exclude
    745    // it from backups, then rename it to the desired name. If we created the
    746    // directory directly where it should be and then excluded it from backups
    747    // we would risk a situation where cargo is interrupted right after the directory
    748    // creation but before the exclusion the directory would remain non-excluded from
    749    // backups because we only perform exclusion right after we created the directory
    750    // ourselves.
    751    //
    752    // We need the tempdir created in parent instead of $TMP, because only then we can be
    753    // easily sure that rename() will succeed (the new name needs to be on the same mount
    754    // point as the old one).
    755    let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
    756    exclude_from_backups(tempdir.path());
    757    exclude_from_content_indexing(tempdir.path());
    758    // Previously std::fs::create_dir_all() (through paths::create_dir_all()) was used
    759    // here to create the directory directly and fs::create_dir_all() explicitly treats
    760    // the directory being created concurrently by another thread or process as success,
    761    // hence the check below to follow the existing behavior. If we get an error at
    762    // rename() and suddenly the directory (which didn't exist a moment earlier) exists
    763    // we can infer from it's another cargo process doing work.
    764    if let Err(e) = fs::rename(tempdir.path(), path) {
    765        if !path.exists() {
    766            return Err(anyhow::Error::from(e))
    767                .with_context(|| format!("failed to create directory `{}`", path.display()));
    768        }
    769    }
    770    Ok(())
    771}
    772
    773/// Mark an existing directory as excluded from backups and indexing.
    774///
    775/// Errors in marking it are ignored.
    776pub fn exclude_from_backups_and_indexing(p: impl AsRef<Path>) {
    777    let path = p.as_ref();
    778    exclude_from_backups(path);
    779    exclude_from_content_indexing(path);
    780}
    781
    782/// Marks the directory as excluded from archives/backups.
    783///
    784/// This is recommended to prevent derived/temporary files from bloating backups. There are two
    785/// mechanisms used to achieve this right now:
    786///
    787/// * A dedicated resource property excluding from Time Machine backups on macOS
    788/// * CACHEDIR.TAG files supported by various tools in a platform-independent way
    789fn exclude_from_backups(path: &Path) {
    790    exclude_from_time_machine(path);
    791    let file = path.join("CACHEDIR.TAG");
    792    if !file.exists() {
    793        let _ = std::fs::write(
    794            file,
    795            "Signature: 8a477f597d28d172789f06886806bc55
    796# This file is a cache directory tag created by cargo.
    797# For information about cache directory tags see https://bford.info/cachedir/
    798",
    799        );
    800        // Similarly to exclude_from_time_machine() we ignore errors here as it's an optional feature.
    801    }
    802}
    803
    804/// Marks the directory as excluded from content indexing.
    805///
    806/// This is recommended to prevent the content of derived/temporary files from being indexed.
    807/// This is very important for Windows users, as the live content indexing significantly slows
    808/// cargo's I/O operations.
    809///
    810/// This is currently a no-op on non-Windows platforms.
    811fn exclude_from_content_indexing(path: &Path) {
    812    #[cfg(windows)]
    813    {
    814        use std::iter::once;
    815        use std::os::windows::prelude::OsStrExt;
    816        use windows_sys::Win32::Storage::FileSystem::{
    817            GetFileAttributesW, SetFileAttributesW, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
    818        };
    819
    820        let path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
    821        unsafe {
    822            SetFileAttributesW(
    823                path.as_ptr(),
    824                GetFileAttributesW(path.as_ptr()) | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
    825            );
    826        }
    827    }
    828    #[cfg(not(windows))]
    829    {
    830        let _ = path;
    831    }
    832}
    833
    834#[cfg(not(target_os = "macos"))]
    835fn exclude_from_time_machine(_: &Path) {}
    836
    837#[cfg(target_os = "macos")]
    838/// Marks files or directories as excluded from Time Machine on macOS
    839fn exclude_from_time_machine(path: &Path) {
    840    use core_foundation::base::TCFType;
    841    use core_foundation::{number, string, url};
    842    use std::ptr;
    843
    844    // For compatibility with 10.7 a string is used instead of global kCFURLIsExcludedFromBackupKey
    845    let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
    846    let path = url::CFURL::from_path(path, false);
    847    if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
    848        unsafe {
    849            url::CFURLSetResourcePropertyForKey(
    850                path.as_concrete_TypeRef(),
    851                is_excluded_key.as_concrete_TypeRef(),
    852                number::kCFBooleanTrue as *const _,
    853                ptr::null_mut(),
    854            );
    855        }
    856    }
    857    // Errors are ignored, since it's an optional feature and failure
    858    // doesn't prevent Cargo from working
    859}
    860
    861#[cfg(test)]
    862mod tests {
    863    use super::join_paths;
    864    use super::normalize_path;
    865    use super::write;
    866    use super::write_atomic;
    867
    868    #[test]
    869    fn test_normalize_path() {
    870        let cases = &[
    871            ("", ""),
    872            (".", ""),
    873            (".////./.", ""),
    874            ("/", "/"),
    875            ("/..", "/"),
    876            ("/foo/bar", "/foo/bar"),
    877            ("/foo/bar/", "/foo/bar"),
    878            ("/foo/bar/./././///", "/foo/bar"),
    879            ("/foo/bar/..", "/foo"),
    880            ("/foo/bar/../..", "/"),
    881            ("/foo/bar/../../..", "/"),
    882            ("foo/bar", "foo/bar"),
    883            ("foo/bar/", "foo/bar"),
    884            ("foo/bar/./././///", "foo/bar"),
    885            ("foo/bar/..", "foo"),
    886            ("foo/bar/../..", ""),
    887            ("foo/bar/../../..", ".."),
    888            ("../../foo/bar", "../../foo/bar"),
    889            ("../../foo/bar/", "../../foo/bar"),
    890            ("../../foo/bar/./././///", "../../foo/bar"),
    891            ("../../foo/bar/..", "../../foo"),
    892            ("../../foo/bar/../..", "../.."),
    893            ("../../foo/bar/../../..", "../../.."),
    894        ];
    895        for (input, expected) in cases {
    896            let actual = normalize_path(std::path::Path::new(input));
    897            assert_eq!(actual, std::path::Path::new(expected), "input: {input}");
    898        }
    899    }
    900
    901    #[test]
    902    fn write_works() {
    903        let original_contents = "[dependencies]\nfoo = 0.1.0";
    904
    905        let tmpdir = tempfile::tempdir().unwrap();
    906        let path = tmpdir.path().join("Cargo.toml");
    907        write(&path, original_contents).unwrap();
    908        let contents = std::fs::read_to_string(&path).unwrap();
    909        assert_eq!(contents, original_contents);
    910    }
    911    #[test]
    912    fn write_atomic_works() {
    913        let original_contents = "[dependencies]\nfoo = 0.1.0";
    914
    915        let tmpdir = tempfile::tempdir().unwrap();
    916        let path = tmpdir.path().join("Cargo.toml");
    917        write_atomic(&path, original_contents).unwrap();
    918        let contents = std::fs::read_to_string(&path).unwrap();
    919        assert_eq!(contents, original_contents);
    920    }
    921
    922    #[test]
    923    #[cfg(unix)]
    924    fn write_atomic_permissions() {
    925        use std::os::unix::fs::PermissionsExt;
    926
    927        let original_perms = std::fs::Permissions::from_mode(u32::from(
    928            libc::S_IRWXU | libc::S_IRGRP | libc::S_IWGRP | libc::S_IROTH,
    929        ));
    930
    931        let tmp = tempfile::Builder::new().tempfile().unwrap();
    932
    933        // need to set the permissions after creating the file to avoid umask
    934        tmp.as_file()
    935            .set_permissions(original_perms.clone())
    936            .unwrap();
    937
    938        // after this call, the file at `tmp.path()` will not be the same as the file held by `tmp`
    939        write_atomic(tmp.path(), "new").unwrap();
    940        assert_eq!(std::fs::read_to_string(tmp.path()).unwrap(), "new");
    941
    942        let new_perms = std::fs::metadata(tmp.path()).unwrap().permissions();
    943
    944        let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
    945        assert_eq!(original_perms.mode(), new_perms.mode() & mask);
    946    }
    947
    948    #[test]
    949    fn join_paths_lists_paths_on_error() {
    950        let valid_paths = vec!["/testing/one", "/testing/two"];
    951        // does not fail on valid input
    952        let _joined = join_paths(&valid_paths, "TESTING1").unwrap();
    953
    954        #[cfg(unix)]
    955        {
    956            let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"];
    957            let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
    958            assert_eq!(
    959                err.to_string(),
    960                "failed to join paths from `$TESTING2` together\n\n\
    961             Check if any of path segments listed below contain an \
    962             unterminated quote character or path separator:\
    963             \n    \"/testing/one\"\
    964             \n    \"/testing/t:wo/three\"\
    965             "
    966            );
    967        }
    968        #[cfg(windows)]
    969        {
    970            let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"];
    971            let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
    972            assert_eq!(
    973                err.to_string(),
    974                "failed to join paths from `$TESTING2` together\n\n\
    975             Check if any of path segments listed below contain an \
    976             unterminated quote character or path separator:\
    977             \n    \"/testing/one\"\
    978             \n    \"/testing/t\\\"wo/three\"\
    979             "
    980            );
    981        }
    982    }
    983
    984    #[test]
    985    #[cfg(windows)]
    986    fn test_remove_symlink_dir() {
    987        use super::*;
    988        use std::fs;
    989        use std::os::windows::fs::symlink_dir;
    990
    991        let tmpdir = tempfile::tempdir().unwrap();
    992        let dir_path = tmpdir.path().join("testdir");
    993        let symlink_path = tmpdir.path().join("symlink");
    994
    995        fs::create_dir(&dir_path).unwrap();
    996
    997        symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
    998
    999        assert!(symlink_path.exists());
    1000
    1001        assert!(remove_file(symlink_path.clone()).is_ok());
    1002
    1003        assert!(!symlink_path.exists());
    1004        assert!(dir_path.exists());
    1005    }
    1006
    1007    #[test]
    1008    #[cfg(windows)]
    1009    fn test_remove_symlink_file() {
    1010        use super::*;
    1011        use std::fs;
    1012        use std::os::windows::fs::symlink_file;
    1013
    1014        let tmpdir = tempfile::tempdir().unwrap();
    1015        let file_path = tmpdir.path().join("testfile");
    1016        let symlink_path = tmpdir.path().join("symlink");
    1017
    1018        fs::write(&file_path, b"test").unwrap();
    1019
    1020        symlink_file(&file_path, &symlink_path).expect("failed to create symlink");
    1021
    1022        assert!(symlink_path.exists());
    1023
    1024        assert!(remove_file(symlink_path.clone()).is_ok());
    1025
    1026        assert!(!symlink_path.exists());
    1027        assert!(file_path.exists());
    1028    }
    1029}