diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 3a16f670b63..24037ed9437 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -118,85 +118,108 @@ fn tail_file( observer: &mut Observer, offset: u64, ) -> UResult<()> { - if !path.exists() { - set_exit_code(1); - show_error!( - "{}", - translate!("tail-error-cannot-open-no-such-file", "file" => input.display_name.clone(), "error" => translate!("tail-no-such-file-or-directory")) - ); - observer.add_bad_path(path, input.display_name.as_str(), false)?; - } else if path.is_dir() { - set_exit_code(1); - - header_printer.print_input(input); - let err_msg = translate!("tail-is-a-directory"); - - show_error!( - "{}", - translate!("tail-error-reading-file", "file" => input.display_name.clone(), "error" => err_msg) - ); - if settings.follow.is_some() { - let msg = if settings.retry { - "" - } else { - &translate!("tail-giving-up-on-this-name") - }; + match path.metadata() { + Ok(md) if md.is_dir() => { + set_exit_code(1); + + header_printer.print_input(input); + let err_msg = translate!("tail-is-a-directory"); + show_error!( "{}", - translate!("tail-error-cannot-follow-file-type", "file" => input.display_name.clone(), "msg" => msg) + translate!( + "tail-error-reading-file", + "file" => input.display_name.clone(), + "error" => err_msg + ) ); + if settings.follow.is_some() { + let msg = if settings.retry { + "" + } else { + &translate!("tail-giving-up-on-this-name") + }; + show_error!( + "{}", + translate!( + "tail-error-cannot-follow-file-type", + "file" => input.display_name.clone(), + "msg" => msg + ) + ); + } + if !observer.follow_name_retry() { + return Ok(()); + } + observer.add_bad_path(path, input.display_name.as_str(), false)?; + return Ok(()); } - if !observer.follow_name_retry() { - // skip directory if not retry + Err(e) if e.kind() == ErrorKind::NotFound => { + set_exit_code(1); + show_error!( + "{}", + translate!( + "tail-error-cannot-open-no-such-file", + "file" => input.display_name.clone(), + "error" => translate!("tail-no-such-file-or-directory") + ) + ); + observer.add_bad_path(path, input.display_name.as_str(), false)?; return Ok(()); } - observer.add_bad_path(path, input.display_name.as_str(), false)?; - } else { - #[cfg(unix)] - let open_result = open_file(path, settings.pid != 0); - #[cfg(not(unix))] - let open_result = File::open(path); - - match open_result { - Ok(mut file) => { - let st = file.metadata()?; - let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st); - header_printer.print_input(input); - let mut reader; - if !settings.presume_input_pipe - && file.is_seekable(if input.is_stdin() { offset } else { 0 }) - && (!st.is_file() || st.len() > blksize_limit) - { - bounded_tail(&mut file, settings); - reader = BufReader::new(file); - } else { - reader = BufReader::new(file); - unbounded_tail(&mut reader, settings)?; - } - if input.is_tailable() { - observer.add_path( - path, - input.display_name.as_str(), - Some(Box::new(reader)), - true, - )?; - } else { - observer.add_bad_path(path, input.display_name.as_str(), false)?; - } - } - Err(e) if e.kind() == ErrorKind::PermissionDenied => { - observer.add_bad_path(path, input.display_name.as_str(), false)?; - show!(e.map_err_context(|| { - translate!("tail-error-cannot-open-for-reading", "file" => input.display_name.clone()) - })); + _ => {} + } + + #[cfg(unix)] + let open_result = open_file(path, settings.pid != 0); + #[cfg(not(unix))] + let open_result = File::open(path); + + match open_result { + Ok(mut file) => { + let st = file.metadata()?; + let blksize_limit = uucore::fs::sane_blksize::sane_blksize_from_metadata(&st); + header_printer.print_input(input); + let mut reader; + if !settings.presume_input_pipe + && file.is_seekable(if input.is_stdin() { offset } else { 0 }) + && (!st.is_file() || st.len() > blksize_limit) + { + bounded_tail(&mut file, settings); + reader = BufReader::new(file); + } else { + reader = BufReader::new(file); + unbounded_tail(&mut reader, settings)?; } - Err(e) => { + if input.is_tailable() { + observer.add_path( + path, + input.display_name.as_str(), + Some(Box::new(reader)), + true, + )?; + } else { observer.add_bad_path(path, input.display_name.as_str(), false)?; - return Err(e.map_err_context(|| { - translate!("tail-error-cannot-open-for-reading", "file" => input.display_name.clone()) - })); } } + Err(e) if e.kind() == ErrorKind::PermissionDenied => { + observer.add_bad_path(path, input.display_name.as_str(), false)?; + show!(e.map_err_context(|| { + translate!( + "tail-error-cannot-open-for-reading", + "file" => input.display_name.clone() + ) + })); + } + Err(e) => { + observer.add_bad_path(path, input.display_name.as_str(), false)?; + return Err(e.map_err_context(|| { + translate!( + "tail-error-cannot-open-for-reading", + "file" => input.display_name.clone() + ) + })); + } } Ok(()) diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 3dd0ee0d2d4..9a0e5a890ab 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -274,6 +274,58 @@ fn test_permission_denied_multiple() { .stdout_is("==> file1 <==\n\n==> file2 <==\n"); } + +// TODO: Add similar test for windows +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_permission_denied_from_metadata_error() { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.mkdir("noaccess"); + at.write("noaccess/secret", "topsecret\n"); + + // Make the directory non-searchable so path lookup fails with EACCES. + fs::set_permissions(at.plus("noaccess"), fs::Permissions::from_mode(0o000)).unwrap(); + + ts.ucmd() + .arg("noaccess/secret") + .fails_with_code(1) + .no_stdout() + .stderr_is("tail: cannot open 'noaccess/secret' for reading: Permission denied\n"); + + // Restore permissions so the test harness can clean up the temp directory. + fs::set_permissions(at.plus("noaccess"), fs::Permissions::from_mode(0o700)).unwrap(); +} + +// Same as above, but ensure we keep processing subsequent files. +// TODO: Add similar test for windows +#[test] +#[cfg(all(unix, not(target_os = "android")))] +fn test_permission_denied_from_metadata_error_multiple() { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("file1"); + at.touch("file2"); + + at.mkdir("noaccess"); + at.write("noaccess/secret", "topsecret\n"); + fs::set_permissions(at.plus("noaccess"), fs::Permissions::from_mode(0o000)).unwrap(); + + ucmd.args(&["file1", "noaccess/secret", "file2"]) + .fails_with_code(1) + .stdout_is("==> file1 <==\n\n==> file2 <==\n") + .stderr_is("tail: cannot open 'noaccess/secret' for reading: Permission denied\n"); + + fs::set_permissions(at.plus("noaccess"), fs::Permissions::from_mode(0o700)).unwrap(); +} + #[test] fn test_follow_redirect_stdin_name_retry() { // $ touch f && tail -F - < f