diff --git a/json/empty.json b/json/empty.json index 0967ef4..e69de29 100644 --- a/json/empty.json +++ b/json/empty.json @@ -1 +0,0 @@ -{} diff --git a/nodelike/src/config.rs b/nodelike/src/config.rs index 899402a..c1fe391 100644 --- a/nodelike/src/config.rs +++ b/nodelike/src/config.rs @@ -70,6 +70,12 @@ pub fn cli_base(name: impl Into) -> clap::Command { .action(ArgAction::SetTrue) ) + .arg( + Arg::new("STRICT") + .help("Strictly conform to format specifications (disables empty-file detection; empty JSON files will produce parse errors)") + .long("strict") + .action(ArgAction::SetTrue) + ) } /// Configuration information @@ -104,6 +110,7 @@ pub struct Config { pub timing: bool, pub mount: Option, pub cleanup_mount: bool, + pub strict: bool, } #[derive(Debug)] @@ -200,6 +207,7 @@ impl Config { config.timing = args.get_flag("TIMING"); config.add_newlines = !args.get_flag("EXACT"); config.allow_xattr = !args.get_flag("NOXATTR"); + config.strict = args.get_flag("STRICT"); // munging policy config.munge = match args.get_one::("MUNGE") { @@ -252,11 +260,19 @@ impl Config { /// Generate a reader for input /// - /// A return of `None` means to start from an empty named directory + /// A return of `None` means to start from an empty named directory. + /// When `--strict` is not set, a zero-byte file is treated as empty. pub fn input_reader(&self) -> Option> { match &self.input { Input::Stdin => Some(Box::new(std::io::stdin())), Input::File(file) => { + if !self.strict + && let Ok(meta) = std::fs::metadata(file) + && meta.len() == 0 + { + debug!("Empty file detected, treating as empty input"); + return None; + } let fmt = self.input_format; let file = std::fs::File::open(file).unwrap_or_else(|e| { error!("Unable to open {} for {fmt} input: {e}", file.display()); @@ -315,6 +331,7 @@ impl Default for Config { timing: false, mount: None, cleanup_mount: false, + strict: false, } } } diff --git a/nodelike/src/nodelike.rs b/nodelike/src/nodelike.rs index 38638fd..284068d 100644 --- a/nodelike/src/nodelike.rs +++ b/nodelike/src/nodelike.rs @@ -187,9 +187,11 @@ where match (*self).node(config) { Node::String(t, s) => Node::String(t, s), Node::Bytes(b) => Node::Bytes(b), - Node::List(vs) => { - Node::List(vs.into_iter().map(|v| Box::new(v) as Box).collect()) - } + Node::List(vs) => Node::List( + vs.into_iter() + .map(|v| Box::new(v) as Box) + .collect(), + ), Node::Map(kvs) => Node::Map( kvs.into_iter() .map(|(k, v)| (k, Box::new(v) as Box)) @@ -281,7 +283,6 @@ pub mod json { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { match typ { Typ::Auto => { @@ -439,7 +440,6 @@ pub mod toml { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { let v = match typ { Typ::Auto => { @@ -653,7 +653,6 @@ pub mod yaml { } } - fn from_string(typ: Typ, contents: String, _config: &Config) -> Self { match typ { Typ::Auto => { diff --git a/pack/src/main.rs b/pack/src/main.rs index 03fa73e..73a5adc 100644 --- a/pack/src/main.rs +++ b/pack/src/main.rs @@ -390,16 +390,19 @@ impl Pack { Resolving type automatically." ); } + let mut count = 0; let all_files_begin_with_num = fs::read_dir(path.clone())? .map(|res| res.map(|e| e.path())) .map(|e| e.unwrap().file_name().unwrap().to_str().unwrap().to_owned()) .all(|filename| { + count += 1; + filename.chars().nth(0).unwrap().is_ascii_digit() || filename.len() > 1 && filename.chars().nth(0).unwrap() == '-' && filename.chars().nth(1).unwrap().is_ascii_digit() }); - if all_files_begin_with_num { + if all_files_begin_with_num && count > 0 { path_type = "list" } else { path_type = "named" diff --git a/run_tests.sh b/run_tests.sh index 0e9f03c..724ea2d 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -32,9 +32,9 @@ then printf "Couldn't find ffs or pack/unpack; building...\n" >&2 (cd "$FFS_TOP" if [ "$(uname -s)" = "Darwin" ] - then - cargo build --bin pack --bin unpack - else + then + cargo build --bin pack --bin unpack + else cargo build --workspace fi) if ! detect_tools @@ -67,7 +67,7 @@ do esac printf "========== STARTING TEST: $tname\n" - (RUST_LOG="ffs=debug,unpack=debug,pack=debug,fuser=debug"; export RUST_LOG; ./${test} >$LOG/$tname.out 2>$LOG/$tname.err; echo $?>$LOG/$tname.ec) & + (RUST_LOG="debug"; export RUST_LOG; ./${test} >$LOG/$tname.out 2>$LOG/$tname.err; echo $?>$LOG/$tname.ec) & : $((TOTAL += 1)) # don't slam 'em diff --git a/tests/ffs.empty_file.sh b/tests/ffs.empty_file.sh new file mode 100755 index 0000000..7af889c --- /dev/null +++ b/tests/ffs.empty_file.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +TIMEOUT="$(cd ../utils; pwd)/timeout" +WAITFOR="$(cd ../utils; pwd)/waitfor" +. ./fail.def + +# --- JSON empty file --- +MNT=$(mktemp -d) + +ffs -m "$MNT" ../json/empty.json & +PID=$! +"$WAITFOR" mount "$MNT" + +[ -z "$(ls "$MNT")" ] || fail json_notempty +"$WAITFOR" umount "$MNT" || fail json_unmount +"$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail json_process +rmdir "$MNT" || fail json_mount + +# --- TOML empty file --- +MNT=$(mktemp -d) + +ffs -m "$MNT" ../toml/empty.toml & +PID=$! +"$WAITFOR" mount "$MNT" + +[ -z "$(ls "$MNT")" ] || fail toml_notempty +"$WAITFOR" umount "$MNT" || fail toml_unmount +"$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail toml_process +rmdir "$MNT" || fail toml_mount + +# --- YAML empty file --- +MNT=$(mktemp -d) + +ffs -m "$MNT" ../yaml/empty.yaml & +PID=$! +"$WAITFOR" mount "$MNT" + +[ -z "$(ls "$MNT")" ] || fail yaml_notempty +"$WAITFOR" umount "$MNT" || fail yaml_unmount +"$WAITFOR" exit $PID +kill -0 $PID >/dev/null 2>&1 && fail yaml_process +rmdir "$MNT" || fail yaml_mount + +# --- --strict should error on empty JSON --- +MNT=$(mktemp -d) + +"$TIMEOUT" -t 2 ffs --strict -m "$MNT" ../json/empty.json 2>/dev/null +[ $? -ne 0 ] || fail strict_should_error +rmdir "$MNT" || fail strict_mount diff --git a/tests/packunpack.empty_file.sh b/tests/packunpack.empty_file.sh new file mode 100755 index 0000000..2937578 --- /dev/null +++ b/tests/packunpack.empty_file.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +TIMEOUT="$(cd ../utils; pwd)/timeout" + +fail() { + echo FAILED: $1 + if [ "$MNT" ] + then + rm -r "$MNT" + fi + exit 1 +} + +# --- JSON empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../json/empty.json || fail json_unpack +[ -z "$(ls "$MNT")" ] || fail json_notempty +rm -r "$MNT" || fail json_cleanup + +# --- TOML empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../toml/empty.toml || fail toml_unpack +[ -z "$(ls "$MNT")" ] || fail toml_notempty +rm -r "$MNT" || fail toml_cleanup + +# --- YAML empty file --- +MNT=$(mktemp -d) +unpack --into "$MNT" ../yaml/empty.yaml || fail yaml_unpack +[ -z "$(ls "$MNT")" ] || fail yaml_notempty +rm -r "$MNT" || fail yaml_cleanup + +# --- --strict should error on empty JSON --- +MNT=$(mktemp -d) +"$TIMEOUT" -t 2 unpack --strict --into "$MNT" ../json/empty.json 2>/dev/null +[ $? -ne 0 ] || fail strict_should_error +rm -r "$MNT" || fail strict_cleanup diff --git a/unpack/src/main.rs b/unpack/src/main.rs index e2c221f..43bcfdb 100644 --- a/unpack/src/main.rs +++ b/unpack/src/main.rs @@ -310,8 +310,10 @@ fn main() -> std::io::Result<()> { let reader = match config.input_reader() { Some(reader) => reader, None => { - error!("Input not specified"); - std::process::exit(ERROR_STATUS_CLI); + // Empty input: the mount directory already exists, so just + // leave it as an empty directory. + info!("Empty input; unpacked into empty directory {mount:?}"); + return Ok(()); } }; diff --git a/utils/waitfor b/utils/waitfor index 42d461f..1eb2071 100755 --- a/utils/waitfor +++ b/utils/waitfor @@ -28,8 +28,8 @@ elapsed=0 while [ "$(echo "$elapsed < $timeout" | bc)" -eq 1 ]; do case "$cmd" in mount) mountpoint -q "$arg" && exit 0 ;; - umount) umount "$arg" 2>/dev/null && exit 0 ;; - exit) kill -0 "$arg" 2>/dev/null || exit 0 ;; + umount) umount "$arg" >/dev/null 2>&1 && exit 0 ;; + exit) kill -0 "$arg" >/dev/null 2>&1 || exit 0 ;; *) echo "waitfor: unknown command '$cmd'" >&2; exit 1 ;; esac sleep "$INTERVAL" @@ -37,10 +37,10 @@ while [ "$(echo "$elapsed < $timeout" | bc)" -eq 1 ]; do done if [ "$cmd" = "exit" ]; then - kill -TERM "$arg" 2>/dev/null - if kill -0 "$arg" 2>/dev/null; then + kill -TERM "$arg" >/dev/null 2>&1 + if kill -0 "$arg" >/dev/null 2>&1; then sleep "$INTERVAL" - kill -KILL "$arg" 2>/dev/null + kill -KILL "$arg" >/dev/null 2>&1 fi fi diff --git a/yaml/empty.yaml b/yaml/empty.yaml new file mode 100644 index 0000000..e69de29