diff --git a/src/updatedb/mod.rs b/src/updatedb/mod.rs index cd3d888..00dd5f4 100644 --- a/src/updatedb/mod.rs +++ b/src/updatedb/mod.rs @@ -14,7 +14,7 @@ use std::{ }; use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command}; -use uucore::error::UResult; +use uucore::error::{strip_errno, UResult, USimpleError}; use crate::find::{find_main, Dependencies}; @@ -323,22 +323,46 @@ fn do_updatedb(args: &[&str]) -> UResult<()> { let deps = CapturedDependencies::new(output.clone()); find_main(find_args.as_slice(), &deps); - let mut writer = BufWriter::new( - OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open(config.output)?, - ); + let output_path = config.output; + let file = OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(&output_path) + .map_err(|e| { + USimpleError::new( + 1, + format!( + "cannot create '{}': {}", + output_path.display(), + strip_errno(&e) + ), + ) + })?; + let mut writer = BufWriter::new(file); + + // strip the trailing "(os error N)" so write failures read like the create error above + let write_err = |e: std::io::Error| { + USimpleError::new( + 1, + format!( + "error writing '{}': {}", + output_path.display(), + strip_errno(&e) + ), + ) + }; let output = output.borrow(); let frcoder = Frcoder::new(output.as_slice(), config.db_format); - writer.write_all(&frcoder.generate_header())?; + writer + .write_all(&frcoder.generate_header()) + .map_err(&write_err)?; for v in frcoder { - writer.write_all(v.as_slice())?; + writer.write_all(v.as_slice()).map_err(&write_err)?; } - writer.flush()?; + writer.flush().map_err(&write_err)?; Ok(()) } diff --git a/tests/db_tests.rs b/tests/db_tests.rs index 04d6349..89e949d 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -196,6 +196,32 @@ fn test_updatedb_empty_prune() { .success(); } +// when the output database can't be created, updatedb must report a clear error naming the path +// and must not leak the raw "(os error N)" suffix +#[test] +fn test_updatedb_output_create_error() { + let tmp = tempfile::tempdir().unwrap(); + // a path under a non-existent directory can't be created + let bad_output = tmp.path().join("does-not-exist").join("db"); + let assert = Command::cargo_bin("updatedb") + .expect("couldn't find updatedb binary") + .args([ + "--localpaths=./test_data".to_string(), + format!("--output={}", bad_output.display()), + ]) + .assert() + .failure(); + let stderr = String::from_utf8_lossy(&assert.get_output().stderr); + assert!( + stderr.contains("cannot create") && stderr.contains(&bad_output.display().to_string()), + "stderr did not name the un-creatable output path: {stderr:?}" + ); + assert!( + !stderr.contains("os error"), + "stderr leaked the raw OS error: {stderr:?}" + ); +} + // build a database from a temp tree with updatedb, then query it back with locate. This is the // only test that exercises the full pipeline (writer + reader) and is platform-independent. #[test]