prs_lib/util/
fs.rs

1use std::path::{Path, PathBuf};
2#[cfg(all(feature = "tomb", target_os = "linux"))]
3use std::process::{Command, Stdio};
4
5use anyhow::Result;
6#[cfg(all(feature = "tomb", target_os = "linux"))]
7use fs_extra::dir::CopyOptions;
8use thiserror::Error;
9
10/// sudo binary.
11#[cfg(all(feature = "tomb", target_os = "linux"))]
12pub const SUDO_BIN: &str = crate::systemd_bin::SUDO_BIN;
13
14/// chown binary.
15pub const CHOWN_BIN: &str = "chown";
16
17/// Calcualte directory size in bytes.
18#[cfg(all(feature = "tomb", target_os = "linux"))]
19pub fn dir_size(path: &Path) -> Result<u64, Err> {
20    fs_extra::dir::get_size(path).map_err(Err::DirSize)
21}
22
23/// Copy contents of one directory to another.
24///
25/// This will only copy directory contents recursively. This will not copy the directory itself.
26#[cfg(all(feature = "tomb", target_os = "linux"))]
27pub fn copy_dir_contents(from: &Path, to: &Path) -> Result<()> {
28    let mut options = CopyOptions::new();
29    options.overwrite = true;
30    options.copy_inside = true;
31    options.content_only = true;
32    Ok(fs_extra::dir::copy(from, to, &options)
33        .map(|_| ())
34        .map_err(Err::CopyDirContents)?)
35}
36
37/// Append a suffix to the filename of a path.
38///
39/// Errors if the path parent or file name could not be determined.
40pub fn append_file_name(path: &Path, suffix: &str) -> Result<PathBuf> {
41    Ok(path.parent().ok_or(Err::NoParent)?.join(format!(
42        "{}{}",
43        path.file_name().ok_or(Err::UnknownName)?.to_string_lossy(),
44        suffix,
45    )))
46}
47
48/// Chown a path to the current process' with `sudo`.
49#[cfg(all(feature = "tomb", target_os = "linux"))]
50pub(crate) fn sudo_chown(path: &Path, uid: u32, gid: u32, recursive: bool) -> Result<()> {
51    // Build command
52    let mut cmd = Command::new(SUDO_BIN);
53    cmd.stdin(Stdio::inherit());
54    cmd.stdout(Stdio::inherit());
55    cmd.stderr(Stdio::inherit());
56    cmd.arg("--");
57    cmd.arg(CHOWN_BIN);
58    if recursive {
59        cmd.arg("--recursive");
60    }
61    cmd.arg(format!("{uid}:{gid}"));
62    cmd.arg(path);
63
64    // Invoke and handle status
65    let status = cmd.status().map_err(Err::SudoChown)?;
66    if status.success() {
67        Ok(())
68    } else {
69        Err(Err::Status(status).into())
70    }
71}
72
73/// Chown a path to the current process' UID/GID with `sudo`.
74#[cfg(all(feature = "tomb", target_os = "linux"))]
75pub(crate) fn sudo_chown_current_user(path: &Path, recursive: bool) -> Result<()> {
76    sudo_chown(
77        path,
78        nix::unistd::Uid::effective().as_raw(),
79        nix::unistd::Gid::effective().as_raw(),
80        recursive,
81    )
82}
83
84#[derive(Debug, Error)]
85pub enum Err {
86    #[cfg(all(feature = "tomb", target_os = "linux"))]
87    #[error("failed to measure directory size")]
88    DirSize(#[source] fs_extra::error::Error),
89
90    #[error("failed to copy directory contents")]
91    #[cfg(all(feature = "tomb", target_os = "linux"))]
92    CopyDirContents(#[source] fs_extra::error::Error),
93
94    #[error("failed to append suffix to file path, unknown parent")]
95    NoParent,
96
97    #[error("failed to append suffix to file path, unknown name")]
98    UnknownName,
99
100    #[error("failed to invoke 'sudo chown' on path")]
101    SudoChown(std::io::Error),
102
103    #[error("system command exited with non-zero status code: {0}")]
104    Status(std::process::ExitStatus),
105}