mirror of
https://github.com/mgree/ffs.git
synced 2024-10-03 22:07:18 +03:00
impl --max-depth and --allow-symlink-escape. tests needed
This commit is contained in:
parent
f2aba26741
commit
a486d7b1bb
153
src/bin/pack.rs
153
src/bin/pack.rs
@ -1,5 +1,5 @@
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
// use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
@ -28,12 +28,14 @@ use ::xattr;
|
||||
|
||||
pub struct Pack {
|
||||
pub symlinks: HashMap<PathBuf, PathBuf>,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
impl Pack {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
symlinks: HashMap::new(),
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +43,14 @@ impl Pack {
|
||||
where
|
||||
V: Nodelike + std::fmt::Display + Default,
|
||||
{
|
||||
if config
|
||||
.max_depth
|
||||
.is_some_and(|max_depth| self.depth > max_depth)
|
||||
{
|
||||
self.depth -= 1;
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// resolve symlink once and map the path to the linked file or
|
||||
// to itself if it is not a symlink
|
||||
if !self.symlinks.contains_key(&path) {
|
||||
@ -64,87 +74,81 @@ impl Pack {
|
||||
match &config.symlink {
|
||||
// early return because we want to ignore this symlink,
|
||||
// but we still add it to self.symlinks above
|
||||
Symlink::NoFollow => return Ok(None),
|
||||
Symlink::Follow(optional_links) => {
|
||||
if optional_links.clone().is_some_and(|links| {
|
||||
links
|
||||
.iter()
|
||||
.find(|link| {
|
||||
// find by inode to avoid PathBuf comparison issue
|
||||
// because of . or .. being unresolved.
|
||||
link.symlink_metadata().unwrap().ino()
|
||||
== path.symlink_metadata().unwrap().ino()
|
||||
})
|
||||
.is_some()
|
||||
}) || optional_links.is_none()
|
||||
{
|
||||
let mut link_trail = Vec::new();
|
||||
let mut link_follower = path.clone();
|
||||
while link_follower.is_symlink() {
|
||||
if link_trail.contains(&link_follower) {
|
||||
error!("Symlink loop detected at {:?}.", link_follower);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
link_trail.push(link_follower.clone());
|
||||
Symlink::NoFollow => {
|
||||
self.depth -= 1;
|
||||
return Ok(None);
|
||||
}
|
||||
Symlink::Follow => {
|
||||
let mut link_trail = Vec::new();
|
||||
let mut link_follower = path.clone();
|
||||
while link_follower.is_symlink() {
|
||||
if link_trail.contains(&link_follower) {
|
||||
error!("Symlink loop detected at {:?}.", link_follower);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
link_trail.push(link_follower.clone());
|
||||
|
||||
if path_type.is_empty() {
|
||||
// get the xattr of the first symlink that has it.
|
||||
// this has the effect of inheriting xattrs from links down the
|
||||
// chain.
|
||||
match xattr::get(&link_follower, "user.type") {
|
||||
Ok(Some(xattr)) if config.allow_xattr => path_type = xattr,
|
||||
Ok(_) => (),
|
||||
// TODO(nad) 2023-08-07: maybe unnecessary to check for ._ as
|
||||
// symlink?
|
||||
Err(_) => {
|
||||
// Cannot call xattr::get on ._ file
|
||||
warn!(
|
||||
"._ files, like {:?}, prevent xattr calls. It will be encoded in base64.",
|
||||
link_follower
|
||||
);
|
||||
path_type = b"bytes".to_vec()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// add the link to the mapping here if it wasn't already added above
|
||||
if !self.symlinks.contains_key(&link_follower) {
|
||||
let link = link_follower.read_link()?;
|
||||
if link.is_absolute() {
|
||||
self.symlinks.insert(link_follower.clone(), link);
|
||||
} else {
|
||||
self.symlinks.insert(
|
||||
link_follower.clone(),
|
||||
link_follower.clone().parent().unwrap().join(link),
|
||||
if path_type.is_empty() {
|
||||
// get the xattr of the first symlink that has it.
|
||||
// this has the effect of inheriting xattrs from links down the
|
||||
// chain.
|
||||
match xattr::get(&link_follower, "user.type") {
|
||||
Ok(Some(xattr)) if config.allow_xattr => path_type = xattr,
|
||||
Ok(_) => (),
|
||||
// TODO(nad) 2023-08-07: maybe unnecessary to check for ._ as
|
||||
// symlink?
|
||||
Err(_) => {
|
||||
// Cannot call xattr::get on ._ file
|
||||
warn!(
|
||||
"._ files, like {:?}, prevent xattr calls. It will be encoded in base64.",
|
||||
link_follower
|
||||
);
|
||||
path_type = b"bytes".to_vec()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// add the link to the mapping here if it wasn't already added above
|
||||
if !self.symlinks.contains_key(&link_follower) {
|
||||
let link = link_follower.read_link()?;
|
||||
if link.is_absolute() {
|
||||
self.symlinks.insert(link_follower.clone(), link);
|
||||
} else {
|
||||
self.symlinks.insert(
|
||||
link_follower.clone(),
|
||||
link_follower.clone().parent().unwrap().join(link),
|
||||
);
|
||||
}
|
||||
link_follower = self.symlinks[&link_follower].clone();
|
||||
}
|
||||
link_follower = self.symlinks[&link_follower].clone();
|
||||
}
|
||||
|
||||
if !link_follower.exists() {
|
||||
error!("The symlink {:?} is broken.", path);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
if !link_follower.exists() {
|
||||
error!("The symlink {:?} is broken.", path);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
|
||||
let canonicalized = link_follower.canonicalize()?;
|
||||
if path.starts_with(&canonicalized) {
|
||||
error!(
|
||||
"The symlink {:?} points to some ancestor directory: {:?}",
|
||||
path, canonicalized
|
||||
);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
} else {
|
||||
// optional links exist but path inode doesn't match that of any
|
||||
// of the specified paths.
|
||||
// early return because we want to ignore this symlink
|
||||
// but we still add it to self.symlinks above
|
||||
// we reached the actual destination
|
||||
let canonicalized = link_follower.canonicalize()?;
|
||||
if path.starts_with(&canonicalized) {
|
||||
error!(
|
||||
"The symlink {:?} points to some ancestor directory: {:?}",
|
||||
path, canonicalized
|
||||
);
|
||||
std::process::exit(ERROR_STATUS_FUSE);
|
||||
}
|
||||
if !config.allow_symlink_escape
|
||||
&& !canonicalized.starts_with(config.mount.clone().unwrap())
|
||||
{
|
||||
warn!("The symlink {:?} points to some file outside of the directory being packed. \
|
||||
Specify --allow-symlink-escape to allow pack to follow this symlink.", path);
|
||||
self.depth -= 1;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the xattr isn't detected yet, either path is not a symlink or
|
||||
// none of the symlinks on the chain have an xattr.
|
||||
if path_type.is_empty() {
|
||||
@ -214,6 +218,7 @@ impl Pack {
|
||||
|
||||
info!("type of {:?} is {}", path, path_type);
|
||||
|
||||
// return the value
|
||||
match path_type {
|
||||
"named" => {
|
||||
let mut children = fs::read_dir(path.clone())?
|
||||
@ -247,6 +252,7 @@ impl Pack {
|
||||
name = child_name.to_string();
|
||||
}
|
||||
}
|
||||
self.depth += 1;
|
||||
let value = self.pack(child.clone(), &config)?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
@ -278,6 +284,7 @@ impl Pack {
|
||||
warn!("skipping ignored file {:?}", child_name);
|
||||
continue;
|
||||
}
|
||||
self.depth += 1;
|
||||
let value = self.pack(child, &config)?;
|
||||
match value {
|
||||
Some(value) => {
|
||||
@ -300,9 +307,13 @@ impl Pack {
|
||||
if config.add_newlines && contents.ends_with('\n') {
|
||||
contents.truncate(contents.len() - 1);
|
||||
}
|
||||
self.depth -= 1;
|
||||
Ok(Some(V::from_string(t, contents, &config)))
|
||||
}
|
||||
Ok(_) | Err(_) => Ok(Some(V::from_bytes(contents, &config))),
|
||||
Ok(_) | Err(_) => {
|
||||
self.depth -= 1;
|
||||
Ok(Some(V::from_bytes(contents, &config)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
|
16
src/cli.rs
16
src/cli.rs
@ -286,23 +286,12 @@ pub fn pack() -> App<'static, 'static> {
|
||||
.help("Never follow symbolic links. This is the default behaviour. `pack` will ignore all symbolic links.")
|
||||
.short("P")
|
||||
.overrides_with("FOLLOW_SYMLINKS")
|
||||
.overrides_with("FOLLOW_SPECIFIED_SYMLINKS")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("FOLLOW_SYMLINKS")
|
||||
.help("Follow all symlinks. For safety, you can also specify a --max-depth value.")
|
||||
.short("L")
|
||||
.overrides_with("NOFOLLOW_SYMLINKS")
|
||||
.overrides_with("FOLLOW_SPECIFIED_SYMLINKS")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("FOLLOW_SPECIFIED_SYMLINKS")
|
||||
.help("Follow all symlinks specified on the command line. For safety, you can also specify a --max-depth value.")
|
||||
.short("H")
|
||||
// .takes_value(true)
|
||||
.min_values(1)
|
||||
.overrides_with("NOFOLLOW_SYMLINKS")
|
||||
.overrides_with("FOLLOW_SYMLINKS")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("MAXDEPTH")
|
||||
@ -310,6 +299,11 @@ pub fn pack() -> App<'static, 'static> {
|
||||
.long("max-depth")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ALLOW_SYMLINK_ESCAPE")
|
||||
.help("Allows pack to follow symlinks outside of the directory being packed.")
|
||||
.long("allow-symlink-escape")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("NOXATTR")
|
||||
.help("Don't use extended attributes to track metadata (see `man xattr`)")
|
||||
|
@ -42,6 +42,7 @@ pub struct Config {
|
||||
pub keep_macos_xattr_file: bool,
|
||||
pub symlink: Symlink,
|
||||
pub max_depth: Option<u32>,
|
||||
pub allow_symlink_escape: bool,
|
||||
pub munge: Munge,
|
||||
pub read_only: bool,
|
||||
pub input: Input,
|
||||
@ -110,7 +111,7 @@ impl FromStr for Munge {
|
||||
#[derive(Debug)]
|
||||
pub enum Symlink {
|
||||
NoFollow,
|
||||
Follow(Option<Vec<PathBuf>>),
|
||||
Follow,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -780,25 +781,12 @@ impl Config {
|
||||
config.add_newlines = !args.is_present("EXACT");
|
||||
config.read_only = args.is_present("READONLY");
|
||||
config.allow_xattr = !args.is_present("NOXATTR");
|
||||
config.allow_symlink_escape = args.is_present("ALLOW_SYMLINK_ESCAPE");
|
||||
config.keep_macos_xattr_file = args.is_present("KEEPMACOSDOT");
|
||||
config.pretty = args.is_present("PRETTY");
|
||||
|
||||
config.symlink = if args.is_present("FOLLOW_SYMLINKS") {
|
||||
Symlink::Follow(None)
|
||||
} else if args.is_present("FOLLOW_SPECIFIED_SYMLINKS") {
|
||||
let cwd = std::env::current_dir().unwrap();
|
||||
Symlink::Follow(Some(
|
||||
args.values_of("FOLLOW_SPECIFIED_SYMLINKS")
|
||||
.unwrap()
|
||||
.map(|p| {
|
||||
if PathBuf::from(p).is_absolute() {
|
||||
PathBuf::from(p)
|
||||
} else {
|
||||
cwd.join(p)
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
Symlink::Follow
|
||||
} else {
|
||||
Symlink::NoFollow
|
||||
};
|
||||
@ -1015,6 +1003,7 @@ impl Default for Config {
|
||||
keep_macos_xattr_file: false,
|
||||
symlink: Symlink::NoFollow,
|
||||
max_depth: None,
|
||||
allow_symlink_escape: false,
|
||||
munge: Munge::Rename,
|
||||
read_only: false,
|
||||
input: Input::Stdin,
|
||||
|
@ -96,9 +96,9 @@ printf '{"a":"a","b":"a","c":"a","d":"a","e":"a","tree":{"about":"tree about","r
|
||||
pack -o "$OUT" -L -- "$MNT" || fail pack2
|
||||
diff "$EXP" "$OUT" || fail "test0 follow"
|
||||
|
||||
printf '{"a":"a","d":"a","tree":{"about":"tree about","root":"tree root"}}' >"$EXP"
|
||||
pack -o "$OUT" -H "$MNT"/d -- "$MNT" || fail pack3
|
||||
diff "$EXP" "$OUT" || fail "test0 follow-specified"
|
||||
# printf '{"a":"a","d":"a","tree":{"about":"tree about","root":"tree root"}}' >"$EXP"
|
||||
# pack -o "$OUT" -H "$MNT"/d -- "$MNT" || fail pack3
|
||||
# diff "$EXP" "$OUT" || fail "test0 follow-specified"
|
||||
|
||||
rm -r "$MNT"
|
||||
mkdir "$MNT"
|
||||
@ -141,9 +141,9 @@ printf '{"ascending":[4,4,4,4,4],"descending":[0,0,0,0,0]}' >"$EXP"
|
||||
pack -o "$OUT" -L -- "$MNT" || fail pack5
|
||||
diff "$EXP" "$OUT" || fail "test1 follow"
|
||||
|
||||
printf '{"ascending":[4,4],"descending":[0,0]}' >"$EXP"
|
||||
pack -o "$OUT" -H "$MNT"/ascending/2 "$MNT"/descending/3 -- "$MNT" || fail pack6
|
||||
diff "$EXP" "$OUT" || fail "test1 follow-specified"
|
||||
# printf '{"ascending":[4,4],"descending":[0,0]}' >"$EXP"
|
||||
# pack -o "$OUT" -H "$MNT"/ascending/2 "$MNT"/descending/3 -- "$MNT" || fail pack6
|
||||
# diff "$EXP" "$OUT" || fail "test1 follow-specified"
|
||||
|
||||
rm -r "$MNT"
|
||||
mkdir "$MNT"
|
||||
@ -175,9 +175,9 @@ printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null
|
||||
pack -o "$OUT" -L -- "$MNT" || fail pack8
|
||||
diff "$EXP" "$OUT" || fail "test2 follow"
|
||||
|
||||
printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null}}}}}' >"$EXP"
|
||||
pack -o "$OUT" -H "$MNT"/path/to/some/link/abs -- "$MNT" || fail pack9
|
||||
diff "$EXP" "$OUT" || fail "test2 follow-specified"
|
||||
# printf '{"path":{"to":{"other":{"file":{"data":null}},"some":{"link":{"abs":null}}}}}' >"$EXP"
|
||||
# pack -o "$OUT" -H "$MNT"/path/to/some/link/abs -- "$MNT" || fail pack9
|
||||
# diff "$EXP" "$OUT" || fail "test2 follow-specified"
|
||||
|
||||
rm -r "$MNT"
|
||||
mkdir "$MNT"
|
||||
@ -266,10 +266,9 @@ if [ "$RUNNER_OS" = "macOS" ] || [ "$(uname)" = "Darwin" ]; then
|
||||
pack -o "$OUT" -L -- "$MNT" || fail pack15
|
||||
diff "$EXP" "$OUT" || fail "test5 follow"
|
||||
|
||||
# TODO(nad) 2023-08-08: maybe only xattrs from followed symlinks are allowed.
|
||||
printf '{"a":4,"b":4,"d":"4","f":"NAo="}' >"$EXP"
|
||||
pack -o "$OUT" -H "$MNT"/b "$MNT"/d "$MNT"/f -- "$MNT" || fail pack16
|
||||
diff "$EXP" "$OUT" || fail "test5 follow-specified"
|
||||
# printf '{"a":4,"b":4,"d":"4","f":"NAo="}' >"$EXP"
|
||||
# pack -o "$OUT" -H "$MNT"/b "$MNT"/d "$MNT"/f -- "$MNT" || fail pack16
|
||||
# diff "$EXP" "$OUT" || fail "test5 follow-specified"
|
||||
fi
|
||||
|
||||
rm "$EXP" "$OUT"
|
||||
|
Loading…
Reference in New Issue
Block a user