catalog: shell: Use the new logic including resolving symlinks for catalog

and remove the old unused code.

Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
Christian Ebner 2020-02-27 17:12:59 +01:00 committed by Dietmar Maurer
parent fee5528e59
commit 32d192a952
1 changed files with 49 additions and 130 deletions

View File

@ -4,7 +4,7 @@ use std::convert::TryFrom;
use std::ffi::{CString, OsStr}; use std::ffi::{CString, OsStr};
use std::io::Write; use std::io::Write;
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
use std::path::Path; use std::path::{Component, Path, PathBuf};
use chrono::{Utc, offset::TimeZone}; use chrono::{Utc, offset::TimeZone};
use failure::*; use failure::*;
@ -105,7 +105,7 @@ impl Shell {
let catalog_root = catalog.root()?; let catalog_root = catalog.root()?;
// The root for the given archive as stored in the catalog // The root for the given archive as stored in the catalog
let archive_root = catalog.lookup(&catalog_root, archive_name.as_bytes())?; let archive_root = catalog.lookup(&catalog_root, archive_name.as_bytes())?;
let root = vec![archive_root]; let path = CatalogPathStack::new(archive_root);
CONTEXT.with(|handle| { CONTEXT.with(|handle| {
let mut ctx = handle.borrow_mut(); let mut ctx = handle.borrow_mut();
@ -113,8 +113,7 @@ impl Shell {
catalog, catalog,
selected: Vec::new(), selected: Vec::new(),
decoder, decoder,
root: root.clone(), path,
current: root,
}); });
}); });
@ -167,12 +166,14 @@ impl Shell {
}; };
let current = if base.is_empty() { let current = if base.is_empty() {
ctx.current.clone() ctx.path.last().clone()
} else { } else {
ctx.canonical_path(base)? let mut local = ctx.path.clone();
local.traverse(&PathBuf::from(base), &mut ctx.decoder, &mut ctx.catalog, false)?;
local.last().clone()
}; };
let entries = match ctx.catalog.read_dir(&current.last().unwrap()) { let entries = match ctx.catalog.read_dir(&current) {
Ok(entries) => entries, Ok(entries) => entries,
Err(_) => return Ok(Vec::new()), Err(_) => return Ok(Vec::new()),
}; };
@ -198,7 +199,7 @@ impl Shell {
/// List the current working directory. /// List the current working directory.
fn pwd_command() -> Result<(), Error> { fn pwd_command() -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
let path = Context::generate_cstring(&ctx.current)?; let path = ctx.path.generate_cstring()?;
let mut out = std::io::stdout(); let mut out = std::io::stdout();
out.write_all(&path.as_bytes())?; out.write_all(&path.as_bytes())?;
out.write_all(&[b'\n'])?; out.write_all(&[b'\n'])?;
@ -222,17 +223,17 @@ fn pwd_command() -> Result<(), Error> {
fn cd_command(path: Option<String>) -> Result<(), Error> { fn cd_command(path: Option<String>) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
let path = path.unwrap_or_default(); let path = path.unwrap_or_default();
let mut path = ctx.canonical_path(&path)?; if path.is_empty() {
if !path ctx.path.clear();
.last() return Ok(());
.ok_or_else(|| format_err!("invalid path component"))? }
.is_directory() let mut local = ctx.path.clone();
{ local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, true)?;
// Change to the parent dir of the file instead if !local.last().is_directory() {
path.pop(); local.pop();
eprintln!("not a directory, fallback to parent directory"); eprintln!("not a directory, fallback to parent directory");
} }
ctx.current = path; ctx.path = local;
Ok(()) Ok(())
}) })
} }
@ -251,19 +252,18 @@ fn cd_command(path: Option<String>) -> Result<(), Error> {
/// List the content of working directory or given path. /// List the content of working directory or given path.
fn ls_command(path: Option<String>) -> Result<(), Error> { fn ls_command(path: Option<String>) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
let parent = if let Some(path) = path { let parent = if let Some(ref path) = path {
ctx.canonical_path(&path)? let mut local = ctx.path.clone();
.last() local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, false)?;
.ok_or_else(|| format_err!("invalid path component"))? local.last().clone()
.clone()
} else { } else {
ctx.current.last().unwrap().clone() ctx.path.last().clone()
}; };
let list = if parent.is_directory() { let list = if parent.is_directory() {
ctx.catalog.read_dir(&parent)? ctx.catalog.read_dir(&parent)?
} else { } else {
vec![parent] vec![parent.clone()]
}; };
if list.is_empty() { if list.is_empty() {
@ -312,13 +312,10 @@ fn ls_command(path: Option<String>) -> Result<(), Error> {
/// which means reading over the network. /// which means reading over the network.
fn stat_command(path: String) -> Result<(), Error> { fn stat_command(path: String) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
// First check if the file exists in the catalog, therefore avoiding let mut local = ctx.path.clone();
// expensive calls to the decoder just to find out that there maybe is local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, false)?;
// no such entry. let canonical = local.canonical(&mut ctx.decoder, &mut ctx.catalog, false)?;
// This is done by calling canonical_path(), which returns the full path let item = canonical.lookup(&mut ctx.decoder)?;
// if it exists, error otherwise.
let path = ctx.canonical_path(&path)?;
let item = ctx.lookup(&path)?;
let mut out = std::io::stdout(); let mut out = std::io::stdout();
out.write_all(b" File:\t")?; out.write_all(b" File:\t")?;
out.write_all(item.filename.as_bytes())?; out.write_all(item.filename.as_bytes())?;
@ -427,10 +424,10 @@ fn stat_command(path: String) -> Result<(), Error> {
/// if an invalid path was provided. /// if an invalid path was provided.
fn select_command(path: String) -> Result<(), Error> { fn select_command(path: String) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
// Calling canonical_path() makes sure the provided path is valid and let mut local = ctx.path.clone();
// actually contained within the catalog and therefore also the archive. local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, false)?;
let path = ctx.canonical_path(&path)?; let canonical = local.canonical(&mut ctx.decoder, &mut ctx.catalog, false)?;
let pattern = MatchPattern::from_line(Context::generate_cstring(&path)?.as_bytes())? let pattern = MatchPattern::from_line(canonical.generate_cstring()?.as_bytes())?
.ok_or_else(|| format_err!("encountered invalid match pattern"))?; .ok_or_else(|| format_err!("encountered invalid match pattern"))?;
if ctx.selected.iter().find(|p| **p == pattern).is_none() { if ctx.selected.iter().find(|p| **p == pattern).is_none() {
ctx.selected.push(pattern); ctx.selected.push(pattern);
@ -455,8 +452,11 @@ fn select_command(path: String) -> Result<(), Error> {
/// selected for restore. /// selected for restore.
fn deselect_command(path: String) -> Result<(), Error> { fn deselect_command(path: String) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
let path = ctx.canonical_path(&path)?; let mut local = ctx.path.clone();
let mut pattern = MatchPattern::from_line(Context::generate_cstring(&path)?.as_bytes())? local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, false)?;
let canonical = local.canonical(&mut ctx.decoder, &mut ctx.catalog, false)?;
println!("{:?}", canonical.generate_cstring()?);
let mut pattern = MatchPattern::from_line(canonical.generate_cstring()?.as_bytes())?
.ok_or_else(|| format_err!("encountered invalid match pattern"))?; .ok_or_else(|| format_err!("encountered invalid match pattern"))?;
if let Some(last) = ctx.selected.last() { if let Some(last) = ctx.selected.last() {
if last == &pattern { if last == &pattern {
@ -529,7 +529,7 @@ fn list_selected_command(pattern: Option<bool>) -> Result<(), Error> {
for pattern in &ctx.selected { for pattern in &ctx.selected {
slices.push(pattern.as_slice()); slices.push(pattern.as_slice());
} }
let mut dir_stack = ctx.root.clone(); let mut dir_stack = vec![ctx.path.root()];
ctx.catalog.find( ctx.catalog.find(
&mut dir_stack, &mut dir_stack,
&slices, &slices,
@ -574,8 +574,8 @@ fn restore_command(target: String, pattern: Option<String>) -> Result<(), Error>
} else { } else {
// Get the directory corresponding to the working directory from the // Get the directory corresponding to the working directory from the
// archive. // archive.
let cwd = ctx.current.clone(); let cwd = ctx.path.clone();
ctx.lookup(&cwd)? cwd.lookup(&mut ctx.decoder)?
}; };
ctx.decoder ctx.decoder
@ -606,13 +606,15 @@ fn restore_command(target: String, pattern: Option<String>) -> Result<(), Error>
/// Find entries in the catalog matching the given match pattern. /// Find entries in the catalog matching the given match pattern.
fn find_command(path: String, pattern: String, select: Option<bool>) -> Result<(), Error> { fn find_command(path: String, pattern: String, select: Option<bool>) -> Result<(), Error> {
Context::with(|ctx| { Context::with(|ctx| {
let path = ctx.canonical_path(&path)?; let mut local = ctx.path.clone();
if !path.last().unwrap().is_directory() { local.traverse(&PathBuf::from(path), &mut ctx.decoder, &mut ctx.catalog, false)?;
let canonical = local.canonical(&mut ctx.decoder, &mut ctx.catalog, false)?;
if !local.last().is_directory() {
bail!("path should be a directory, not a file!"); bail!("path should be a directory, not a file!");
} }
let select = select.unwrap_or(false); let select = select.unwrap_or(false);
let cpath = Context::generate_cstring(&path).unwrap(); let cpath = canonical.generate_cstring().unwrap();
let pattern = if pattern.starts_with("!") { let pattern = if pattern.starts_with("!") {
let mut buffer = vec![b'!']; let mut buffer = vec![b'!'];
buffer.extend_from_slice(cpath.as_bytes()); buffer.extend_from_slice(cpath.as_bytes());
@ -631,7 +633,7 @@ fn find_command(path: String, pattern: String, select: Option<bool>) -> Result<(
// The match pattern all contain the prefix of the entry path in order to // The match pattern all contain the prefix of the entry path in order to
// store them if selected, so the entry point for find is always the root // store them if selected, so the entry point for find is always the root
// directory. // directory.
let mut dir_stack = ctx.root.clone(); let mut dir_stack = vec![ctx.path.root()];
ctx.catalog.find( ctx.catalog.find(
&mut dir_stack, &mut dir_stack,
&slice, &slice,
@ -660,11 +662,8 @@ struct Context {
selected: Vec<MatchPattern>, selected: Vec<MatchPattern>,
/// Decoder instance for the current pxar archive /// Decoder instance for the current pxar archive
decoder: Decoder, decoder: Decoder,
/// Root directory for the give archive as stored in the catalog /// Handle catalog stuff
root: Vec<DirEntry>, path: CatalogPathStack,
/// Stack of directories up to the current working directory
/// used for navigation and path completion.
current: Vec<DirEntry>,
} }
impl Context { impl Context {
@ -692,97 +691,17 @@ impl Context {
Ok(unsafe { CString::from_vec_unchecked(path) }) Ok(unsafe { CString::from_vec_unchecked(path) })
} }
/// Resolve the indirect path components and return an absolute path.
///
/// This will actually navigate the filesystem tree to check that the
/// path is vaild and exists.
/// This does not include following symbolic links.
/// If None is given as path, only the root directory is returned.
fn canonical_path(&mut self, path: &str) -> Result<Vec<DirEntry>, Error> {
if path == "/" {
return Ok(self.root.clone());
}
let mut path_slice = if path.is_empty() {
// Fallback to root if no path was provided
return Ok(self.root.clone());
} else {
path
};
let mut dir_stack = if path_slice.starts_with("/") {
// Absolute path, reduce view of slice and start from root
path_slice = &path_slice[1..];
self.root.clone()
} else {
// Relative path, start from current working directory
self.current.clone()
};
let should_end_dir = if path_slice.ends_with("/") {
path_slice = &path_slice[0..path_slice.len() - 1];
true
} else {
false
};
for name in path_slice.split('/') {
match name {
"" => continue, // Multiple successive slashes are valid and treated as one.
"." => continue,
".." => {
// Never pop archive root from stack
if dir_stack.len() > 1 {
dir_stack.pop();
}
}
_ => {
let entry = self.catalog.lookup(dir_stack.last().unwrap(), name.as_bytes())?;
dir_stack.push(entry);
}
}
}
if should_end_dir
&& !dir_stack
.last()
.ok_or_else(|| format_err!("invalid path component"))?
.is_directory()
{
bail!("entry is not a directory");
}
Ok(dir_stack)
}
/// Generate the CString to display by readline based on /// Generate the CString to display by readline based on
/// PROMPT_PREFIX, PROMPT and the current working directory. /// PROMPT_PREFIX, PROMPT and the current working directory.
fn generate_prompt(&self) -> Result<String, Error> { fn generate_prompt(&self) -> Result<String, Error> {
let prompt = format!( let prompt = format!(
"{}{} {} ", "{}{} {} ",
PROMPT_PREFIX, PROMPT_PREFIX,
Self::generate_cstring(&self.current)?.to_string_lossy(), self.path.generate_cstring()?.to_string_lossy(),
PROMPT, PROMPT,
); );
Ok(prompt) Ok(prompt)
} }
/// Look up the entry given by a canonical absolute `path` in the archive.
///
/// This will actively navigate the archive by calling the corresponding
/// decoder functionalities and is therefore very expensive.
fn lookup(&mut self, absolute_path: &[DirEntry]) -> Result<DirectoryEntry, Error> {
let mut current = self.decoder.root()?;
// Ignore the archive root, don't need it.
for item in absolute_path.iter().skip(1) {
match self
.decoder
.lookup(&current, &OsStr::from_bytes(&item.name))?
{
Some(item) => current = item,
// This should not happen if catalog an archive are consistent.
None => bail!("no such file or directory in archive - inconsistent catalog"),
}
}
Ok(current)
}
} }
/// A valid path in the catalog starting from root. /// A valid path in the catalog starting from root.