prs_lib/
tomb_bin.rs

1use std::env;
2use std::ffi::OsStr;
3use std::path::Path;
4use std::process::{Command, ExitStatus};
5
6use anyhow::Result;
7use thiserror::Error;
8
9use crate::crypto::Key;
10use crate::util;
11
12/// Binary name.
13pub const BIN_NAME: &str = "tomb";
14
15/// Invoke tomb dig.
16///
17/// `mbs` is the size of the tomb to create in megabytes.
18pub fn tomb_dig(tomb_file: &Path, mbs: u32, settings: TombSettings) -> Result<()> {
19    tomb(
20        [
21            "dig",
22            tomb_file
23                .to_str()
24                .expect("tomb path has invalid UTF-8 characters"),
25            "-s",
26            &format!("{mbs}"),
27        ],
28        settings,
29    )
30}
31
32/// Invoke tomb forge.
33pub fn tomb_forge(key_file: &Path, key: &Key, settings: TombSettings) -> Result<()> {
34    tomb(
35        [
36            "forge",
37            key_file
38                .to_str()
39                .expect("tomb key path has invalid UTF-8 characters"),
40            "-gr",
41            &key.fingerprint(false),
42        ],
43        settings,
44    )
45}
46
47/// Invoke tomb lock.
48pub fn tomb_lock(
49    tomb_file: &Path,
50    key_file: &Path,
51    key: &Key,
52    settings: TombSettings,
53) -> Result<()> {
54    tomb(
55        [
56            "lock",
57            tomb_file
58                .to_str()
59                .expect("tomb path has invalid UTF-8 characters"),
60            "-k",
61            key_file
62                .to_str()
63                .expect("tomb key path has invalid UTF-8 characters"),
64            "-gr",
65            &key.fingerprint(false),
66        ],
67        settings,
68    )
69}
70
71/// Invoke tomb open.
72pub fn tomb_open(
73    tomb_file: &Path,
74    key_file: &Path,
75    store_dir: &Path,
76    key: Option<&Key>,
77    settings: TombSettings,
78) -> Result<()> {
79    // Build command arguments list
80    let key_fp = key.map(|key| key.fingerprint(false));
81    let mut args = vec![
82        "open",
83        tomb_file
84            .to_str()
85            .expect("tomb path contains invalid UTF-8"),
86        "-k",
87        key_file
88            .to_str()
89            .expect("tomb key path contains invalid UTF-8"),
90        "-p",
91    ];
92    match &key_fp {
93        Some(fp) => args.extend(&["-gr", fp]),
94        None => args.extend(&["-g"]),
95    }
96    args.push(
97        store_dir
98            .to_str()
99            .expect("password store directory path contains invalid UTF-8"),
100    );
101
102    // TODO: ensure tomb file, key and store dir exist
103    tomb(&args, settings)
104}
105
106/// Invoke tomb close.
107pub fn tomb_close(tomb_file: &Path, settings: TombSettings) -> Result<()> {
108    tomb(
109        ["close", name(tomb_file).expect("failed to get tomb name")],
110        settings,
111    )
112}
113
114/// Invoke tomb slam.
115pub fn tomb_slam(settings: TombSettings) -> Result<()> {
116    tomb(["slam"], settings)
117}
118
119/// Invoke tomb resize.
120pub fn tomb_resize(
121    tomb_file: &Path,
122
123    key_file: &Path,
124    size_mb: u32,
125    settings: TombSettings,
126) -> Result<()> {
127    tomb(
128        [
129            "resize",
130            tomb_file
131                .to_str()
132                .expect("tomb path contains invalid UTF-8"),
133            "-k",
134            key_file
135                .to_str()
136                .expect("tomb key path contains invalid UTF-8"),
137            "-g",
138            "-s",
139            &format!("{size_mb}"),
140        ],
141        settings,
142    )
143}
144
145/// Get tomb name based on path.
146pub fn name(path: &Path) -> Option<&str> {
147    path.file_name()?.to_str()?.rsplitn(2, '.').last()
148}
149
150/// Invoke a tomb command with the given arguments.
151///
152/// The command will take over the user console for in/output.
153fn tomb<I, S>(args: I, settings: TombSettings) -> Result<()>
154where
155    I: IntoIterator<Item = S>,
156    S: AsRef<OsStr>,
157{
158    cmd_assert_status(cmd_tomb(args, settings).status().map_err(Err::Tomb)?)
159}
160
161/// Build a tomb command to run.
162fn cmd_tomb<I, S>(args: I, settings: TombSettings) -> Command
163where
164    I: IntoIterator<Item = S>,
165    S: AsRef<OsStr>,
166{
167    // Build base command
168    let mut cmd = if let Ok(bin) = env::var("PASSWORD_STORE_TOMB") {
169        Command::new(bin)
170    } else {
171        Command::new(BIN_NAME)
172    };
173
174    // Explicitly set GPG_TTY on Wayland to current stdin if not set
175    // Fixed GPG not showing pinentry prompt properly on Wayland
176    // Issue: https://github.com/timvisee/prs/issues/8#issuecomment-871090949
177    if util::env::is_wayland()
178        && !util::env::has_gpg_tty()
179        && let Some(tty) = util::tty::get_tty()
180    {
181        cmd.env("GPG_TTY", tty);
182    }
183
184    // Set global flags, add arguments
185    if settings.quiet {
186        cmd.arg("-q");
187    }
188    if settings.verbose {
189        cmd.arg("-D");
190    }
191    if settings.force {
192        cmd.arg("-f");
193    }
194    cmd.args(args);
195
196    cmd
197}
198
199/// Assert the exit status of a command.
200///
201/// Returns error is status is not succesful.
202fn cmd_assert_status(status: ExitStatus) -> Result<()> {
203    if !status.success() {
204        return Err(Err::Status(status).into());
205    }
206    Ok(())
207}
208
209/// Tomb command settings.
210#[derive(Copy, Clone)]
211pub struct TombSettings {
212    /// Run in quiet (-q) mode.
213    pub quiet: bool,
214
215    /// Run in verbose (-D) mode.
216    pub verbose: bool,
217
218    /// Run with force (-f).
219    pub force: bool,
220}
221
222#[derive(Debug, Error)]
223pub enum Err {
224    #[error("failed to invoke tomb command")]
225    Tomb(#[source] std::io::Error),
226
227    #[error("tomb operation exited with non-zero status code: {0}")]
228    Status(std::process::ExitStatus),
229}