Properly handle errors when following a symlink

Fixes #123. The code assumes that every File that has its link_target() method called would first have been checked to make sure it’s actually a link first. Unfortunately it also assumed that the only thing that can go wrong while following a link is if the file wasn’t a link, meaning it crashes when given a link it doesn’t have permission to follow.

This makes the file_target() method able to return either a file or path for displaying, as before, but also an IO error for when things go wrong.
This commit is contained in:
Ben S 2016-10-29 20:27:23 +01:00
parent ba366fc855
commit 74358c188a
5 changed files with 44 additions and 18 deletions

View File

@ -3,6 +3,7 @@
use std::ascii::AsciiExt;
use std::env::current_dir;
use std::fs;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
@ -162,10 +163,10 @@ impl<'dir> File<'dir> {
/// If statting the file fails (usually because the file on the
/// other end doesn't exist), returns the path to the file
/// that should be there.
pub fn link_target(&self) -> Result<File<'dir>, PathBuf> {
pub fn link_target(&self) -> FileTarget<'dir> {
let path = match fs::read_link(&self.path) {
Ok(path) => path,
Err(_) => panic!("This was not a link!"),
Err(e) => return FileTarget::Err(e),
};
let target_path = match self.dir {
@ -180,7 +181,7 @@ impl<'dir> File<'dir> {
// Use plain `metadata` instead of `symlink_metadata` - we *want* to follow links.
if let Ok(metadata) = fs::metadata(&target_path) {
Ok(File {
FileTarget::Ok(File {
path: target_path.to_path_buf(),
dir: self.dir,
metadata: metadata,
@ -189,7 +190,7 @@ impl<'dir> File<'dir> {
})
}
else {
Err(target_path)
FileTarget::Broken(target_path)
}
}
@ -425,6 +426,23 @@ fn ext(path: &Path) -> Option<String> {
}
/// The result of following a symlink.
pub enum FileTarget<'dir> {
/// The symlink pointed at a file that exists.
Ok(File<'dir>),
/// The symlink pointed at a file that does not exist. Holds the path
/// where the file would be, if it existed.
Broken(PathBuf),
/// There was an IO error when following the link. This can happen if the
/// file isn't a link to begin with, but also if, say, we don't have
/// permission to follow it.
Err(IOError),
}
#[cfg(test)]
mod test {
use super::ext;

View File

@ -2,7 +2,7 @@ mod dir;
pub use self::dir::Dir;
mod file;
pub use self::file::File;
pub use self::file::{File, FileTarget};
pub mod feature;
pub mod fields;

View File

@ -1,6 +1,6 @@
use ansi_term::Style;
use fs::File;
use fs::{File, FileTarget};
pub use self::cell::{TextCell, TextCellContents, DisplayWidth};
pub use self::colours::Colours;
@ -42,7 +42,7 @@ pub fn filename(file: &File, colours: &Colours, links: bool) -> TextCellContents
if links && file.is_link() {
match file.link_target() {
Ok(target) => {
FileTarget::Ok(target) => {
bits.push(Style::default().paint(" "));
bits.push(colours.punctuation.paint("->"));
bits.push(Style::default().paint(" "));
@ -67,12 +67,16 @@ pub fn filename(file: &File, colours: &Colours, links: bool) -> TextCellContents
}
},
Err(broken_path) => {
FileTarget::Broken(broken_path) => {
bits.push(Style::default().paint(" "));
bits.push(colours.broken_arrow.paint("->"));
bits.push(Style::default().paint(" "));
bits.push(colours.broken_filename.paint(broken_path.display().to_string()));
},
FileTarget::Err(_) => {
// Do nothing -- the error gets displayed on the next line
}
}
}
@ -84,7 +88,7 @@ pub fn file_colour(colours: &Colours, file: &File) -> Style {
f if f.is_directory() => colours.filetypes.directory,
f if f.is_executable_file() => colours.filetypes.executable,
f if f.is_link() => colours.filetypes.symlink,
f if f.is_pipe() => colours.filetypes.pipe,
f if f.is_pipe() => colours.filetypes.pipe,
f if f.is_char_device()
| f.is_block_device() => colours.filetypes.device,
f if f.is_socket() => colours.filetypes.socket,

2
xtests/proc_1_root Normal file
View File

@ -0,0 +1,2 @@
lrwxrwxrwx 0 root 29 Oct 17:23 /proc/1/root
└── <Permission denied (os error 13)>

View File

@ -52,5 +52,7 @@ $exa $testcases/links -T 2>&1 | diff -q - $results/links_T || exit 1
COLUMNS=80 $exa $testcases/links 2>&1 | diff -q - $results/links || exit 1
$exa /proc/1/root -l 2>&1 | diff - $results/proc_1_root || exit 1
echo "All the tests passed!"