From 393128bc629ce584d12b1d326a8ca65760861e66 Mon Sep 17 00:00:00 2001 From: clonejo Date: Tue, 4 Feb 2025 17:30:15 +0100 Subject: [PATCH 1/2] Pass password via stdin, not an env var #80 Fixes #80 Contains a workaround for https://github.com/restic/restic/issues/5236 --- src/restic.rs | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/restic.rs b/src/restic.rs index ebccea6..2c1b01a 100644 --- a/src/restic.rs +++ b/src/restic.rs @@ -6,7 +6,7 @@ use std::{ collections::HashSet, ffi::OsStr, fmt::{self, Display, Formatter}, - io::{self, BufRead, BufReader, Lines, Read}, + io::{self, BufRead, BufReader, Lines, Read, Write}, iter::Step, marker::PhantomData, mem, @@ -218,25 +218,41 @@ impl Restic { Repository::File(file) => cmd.arg("--repository-file").arg(file), }; match &self.password { - Password::Command(command) => - cmd.arg("--password-command").arg(command), - Password::File(file) => cmd.arg("--password-file").arg(file), - // There's no way to hand over the password to restic directly, so we use the env - // variable instead. - Password::Plain(str) => cmd.env("RESTIC_PASSWORD", str), + Password::Command(command) => { + cmd.arg("--password-command").arg(command); + cmd.stdin(Stdio::null()); + } + Password::File(file) => { + cmd.arg("--password-file").arg(file); + cmd.stdin(Stdio::null()); + } + Password::Plain(_) => { + // passed via stdin after the process is started + cmd.stdin(Stdio::piped()); + } }; if self.no_cache { cmd.arg("--no-cache"); } cmd.arg("--json"); + // pass --quiet to remove informational messages in stdout mixed up with the JSON we want + // (https://github.com/restic/restic/issues/5236) + cmd.arg("--quiet"); cmd.args(args); - let child = cmd - .stdin(Stdio::null()) + let mut child = cmd .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .map_err(LaunchError)?; info!("running \"{cmd:?}\" (pid {})", child.id()); + if let Password::Plain(ref password) = self.password { + let mut stdin = child + .stdin + .take() + .expect("child has no stdin when it should have"); + stdin.write_all(password.as_bytes()).map_err(LaunchError)?; + stdin.write_all(b"\n").map_err(LaunchError)?; + } Ok(child) } } @@ -261,6 +277,8 @@ impl Iter { fn read_stderr(&mut self, kind: ErrorKind) -> Result { let mut buf = String::new(); + // FIXME: read_to_string can block forever if child's stdout is not read + // this means we never exit and the error is not shown or logged match self.child.stderr.take().unwrap().read_to_string(&mut buf) { Err(e) => Err(Error { kind: ErrorKind::Run(RunError::Io(e)), From 58ef7df448e2ba74add786bc2d5539ef0e16df74 Mon Sep 17 00:00:00 2001 From: clonejo Date: Tue, 4 Feb 2025 20:07:59 +0100 Subject: [PATCH 2/2] Don't wait forever if an error occurs while restic is still running --- src/restic.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/restic.rs b/src/restic.rs index 2c1b01a..38a5e14 100644 --- a/src/restic.rs +++ b/src/restic.rs @@ -277,8 +277,8 @@ impl Iter { fn read_stderr(&mut self, kind: ErrorKind) -> Result { let mut buf = String::new(); - // FIXME: read_to_string can block forever if child's stdout is not read - // this means we never exit and the error is not shown or logged + // read_to_string would block forever if the child was still running. + let _ = self.child.kill(); match self.child.stderr.take().unwrap().read_to_string(&mut buf) { Err(e) => Err(Error { kind: ErrorKind::Run(RunError::Io(e)),