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:
parent
fee5528e59
commit
32d192a952
@ -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(¤t.last().unwrap()) {
|
let entries = match ctx.catalog.read_dir(¤t) {
|
||||||
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(¤t, &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.
|
||||||
|
Loading…
Reference in New Issue
Block a user