1
1
mirror of https://github.com/mgree/ffs.git synced 2024-10-05 15:18:20 +03:00

Exit status (#44)

Unify exit status (0=success, 1=fs error, 2=cli error).

Documented and tested; one test is disabled due to an upstream error masking.
This commit is contained in:
Michael Greenberg 2021-07-21 08:10:42 -07:00 committed by GitHub
parent 747301c815
commit b651c9c1ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 319 additions and 41 deletions

View File

@ -4,6 +4,8 @@
* Handle failed mounts better, with an appropriate message and error
code.
* Revise exit codes: 0 means success, 1 means FS error, 2 means CLI
error.
## 0.1.1 - 2021-07-15

View File

@ -20,7 +20,7 @@ _ffs() {
case "${cmd}" in
ffs)
opts=" -q -d -i -h -V -u -g -o -s -t -m --quiet --debug --exact --unpadded --readonly --no-output --in-place --help --version --completions --uid --gid --mode --dirmode --output --source --target --mount <INPUT> "
opts=" -q -d -i -h -V -u -g -o -s -t -m --quiet --debug --exact --no-xattr --keep-macos-xattr --unpadded --readonly --no-output --in-place --pretty --help --version --completions --uid --gid --mode --dirmode --munge --output --source --target --mount --new <INPUT> "
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -55,6 +55,10 @@ _ffs() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--munge)
COMPREPLY=($(compgen -W "filter rename" -- "${cur}"))
return 0
;;
--output)
COMPREPLY=($(compgen -f "${cur}"))
return 0
@ -87,6 +91,10 @@ _ffs() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--new)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;

View File

@ -1,18 +1,23 @@
complete -c ffs -n "__fish_use_subcommand" -l completions -d 'Generate shell completions and exit' -r -f -a "bash fish zsh"
complete -c ffs -n "__fish_use_subcommand" -l completions -d 'Generate shell completions (and exits)' -r -f -a "bash fish zsh"
complete -c ffs -n "__fish_use_subcommand" -s u -l uid -d 'Sets the user id of the generated filesystem (defaults to current effective user id)'
complete -c ffs -n "__fish_use_subcommand" -s g -l gid -d 'Sets the group id of the generated filesystem (defaults to current effective group id)'
complete -c ffs -n "__fish_use_subcommand" -l mode -d 'Sets the default mode of files (parsed as octal; defaults to 644; if unspecified, directories will have this mode with execute bits set when read bits are set)'
complete -c ffs -n "__fish_use_subcommand" -l dirmode -d 'Sets the default mode of directories (parsed as octal; defaults to 755; )'
complete -c ffs -n "__fish_use_subcommand" -l mode -d 'Sets the default mode of files (parsed as octal)'
complete -c ffs -n "__fish_use_subcommand" -l dirmode -d 'Sets the default mode of directories (parsed as octal; if unspecified, directories will have FILEMODE with execute bits set when read bits are set)'
complete -c ffs -n "__fish_use_subcommand" -l munge -d 'Set the name munging policy; applies to \'.\', \'..\', and files with NUL and \'/\' in them' -r -f -a "filter rename"
complete -c ffs -n "__fish_use_subcommand" -s o -l output -d 'Sets the output file for saving changes (defaults to stdout)'
complete -c ffs -n "__fish_use_subcommand" -s s -l source -d 'Specify the source format explicitly (by default, automatically inferred from filename extension)' -r -f -a "json toml yaml"
complete -c ffs -n "__fish_use_subcommand" -s t -l target -d 'Specify the target format explicitly (by default, automatically inferred from filename extension)' -r -f -a "json toml yaml"
complete -c ffs -n "__fish_use_subcommand" -s m -l mount -d 'Sets the mountpoint; will be inferred when using a file, but must be specified when running on stdin'
complete -c ffs -n "__fish_use_subcommand" -l new -d 'Mounts an empty filesystem, inferring a mountpoint and output format'
complete -c ffs -n "__fish_use_subcommand" -s q -l quiet -d 'Quiet mode (turns off all errors and warnings, enables `--no-output`)'
complete -c ffs -n "__fish_use_subcommand" -s d -l debug -d 'Give debug output on stderr'
complete -c ffs -n "__fish_use_subcommand" -l exact -d 'Don\'t add newlines to the end of values that don\'t already have them (or strip them when loading)'
complete -c ffs -n "__fish_use_subcommand" -l no-xattr -d 'Don\'t use extended attributes to track metadata (see `man xattr`)'
complete -c ffs -n "__fish_use_subcommand" -l keep-macos-xattr -d 'Include ._* extended attribute/resource fork files on macOS'
complete -c ffs -n "__fish_use_subcommand" -l unpadded -d 'Don\'t pad the numeric names of list elements with zeroes; will not sort properly'
complete -c ffs -n "__fish_use_subcommand" -l readonly -d 'Mounted filesystem will be readonly'
complete -c ffs -n "__fish_use_subcommand" -l no-output -d 'Disables output of filesystem (normally on stdout)'
complete -c ffs -n "__fish_use_subcommand" -s i -l in-place -d 'Writes the output back over the input file'
complete -c ffs -n "__fish_use_subcommand" -l pretty -d 'Pretty-print output (may increase size)'
complete -c ffs -n "__fish_use_subcommand" -s h -l help -d 'Prints help information'
complete -c ffs -n "__fish_use_subcommand" -s V -l version -d 'Prints version information'

View File

@ -15,13 +15,14 @@ _ffs() {
local context curcontext="$curcontext" state line
_arguments "${_arguments_options[@]}" \
'--completions=[Generate shell completions and exit]: :(bash fish zsh)' \
'--completions=[Generate shell completions (and exits)]: :(bash fish zsh)' \
'-u+[Sets the user id of the generated filesystem (defaults to current effective user id)]' \
'--uid=[Sets the user id of the generated filesystem (defaults to current effective user id)]' \
'-g+[Sets the group id of the generated filesystem (defaults to current effective group id)]' \
'--gid=[Sets the group id of the generated filesystem (defaults to current effective group id)]' \
'--mode=[Sets the default mode of files (parsed as octal; defaults to 644; if unspecified, directories will have this mode with execute bits set when read bits are set)]' \
'--dirmode=[Sets the default mode of directories (parsed as octal; defaults to 755; )]' \
'--mode=[Sets the default mode of files (parsed as octal)]' \
'--dirmode=[Sets the default mode of directories (parsed as octal; if unspecified, directories will have FILEMODE with execute bits set when read bits are set)]' \
'--munge=[Set the name munging policy; applies to '\''.'\'', '\''..'\'', and files with NUL and '\''/'\'' in them]: :(filter rename)' \
'-o+[Sets the output file for saving changes (defaults to stdout)]' \
'--output=[Sets the output file for saving changes (defaults to stdout)]' \
'-s+[Specify the source format explicitly (by default, automatically inferred from filename extension)]: :(json toml yaml)' \
@ -30,21 +31,25 @@ _ffs() {
'--target=[Specify the target format explicitly (by default, automatically inferred from filename extension)]: :(json toml yaml)' \
'-m+[Sets the mountpoint; will be inferred when using a file, but must be specified when running on stdin]' \
'--mount=[Sets the mountpoint; will be inferred when using a file, but must be specified when running on stdin]' \
'(-i --in-place -s --source -o --output)--new=[Mounts an empty filesystem, inferring a mountpoint and output format]' \
'-q[Quiet mode (turns off all errors and warnings, enables `--no-output`)]' \
'--quiet[Quiet mode (turns off all errors and warnings, enables `--no-output`)]' \
'-d[Give debug output on stderr]' \
'--debug[Give debug output on stderr]' \
'--exact[Don'\''t add newlines to the end of values that don'\''t already have them (or strip them when loading)]' \
'--no-xattr[Don'\''t use extended attributes to track metadata (see `man xattr`)]' \
'--keep-macos-xattr[Include ._* extended attribute/resource fork files on macOS]' \
'--unpadded[Don'\''t pad the numeric names of list elements with zeroes; will not sort properly]' \
'--readonly[Mounted filesystem will be readonly]' \
'--no-output[Disables output of filesystem (normally on stdout)]' \
'-i[Writes the output back over the input file]' \
'--in-place[Writes the output back over the input file]' \
'(--no-output -q --quiet)--pretty[Pretty-print output (may increase size)]' \
'-h[Prints help information]' \
'--help[Prints help information]' \
'-V[Prints version information]' \
'--version[Prints version information]' \
'::INPUT -- Sets the input file (defaults to '-', meaning STDIN):_files' \
'::INPUT -- Sets the input file ('-' means STDIN):_files' \
&& ret=0
}

View File

@ -238,6 +238,20 @@ RUST_LOG
*ffs=warn*. Setting *-q* turns off all output; setting *-d* sets
*ffs=debug*.
# EXIT STATUS
0
: Successfully unmounted.
1
: A FUSE or other filesystem error occurred.
2
: Command-line argument parsing error.
# EXAMPLES
The general workflow is to run *ffs*, do some work, and then unmount

View File

@ -233,6 +233,16 @@ probably be \f[I]ffs\f[R] and \f[I]level\f[R] should be one of
The default is \f[I]ffs=warn\f[R].
Setting \f[I]-q\f[R] turns off all output; setting \f[I]-d\f[R] sets
\f[I]ffs=debug\f[R].
.SH EXIT STATUS
.TP
0
Successfully unmounted.
.TP
1
A FUSE or other filesystem error occurred.
.TP
2
Command-line argument parsing error.
.SH EXAMPLES
.PP
The general workflow is to run \f[I]ffs\f[R], do some work, and then

View File

@ -12,6 +12,9 @@ use super::format::Format;
use super::cli;
pub const ERROR_STATUS_FUSE: i32 = 1;
pub const ERROR_STATUS_CLI: i32 = 2;
/// Configuration information
///
/// See `cli.rs` for information on the actual command-line options; see
@ -100,7 +103,12 @@ impl FromStr for Munge {
impl Config {
/// Parses arguments from `std::env::Args`, via `cli::app().get_matches()`
pub fn from_args() -> Self {
let args = cli::app().get_matches();
let args = cli::app()
.get_matches_safe()
.unwrap_or_else(|e| {
eprintln!("{}", e.message);
std::process::exit(ERROR_STATUS_CLI)
});
let mut config = Config::default();
// generate completions?
@ -115,7 +123,7 @@ impl Config {
clap::Shell::Zsh
} else {
eprintln!("Can't generate completions for '{}'.", shell);
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
};
cli::app().gen_completions_to("ffs", shell, &mut std::io::stdout());
std::process::exit(0);
@ -166,7 +174,7 @@ impl Config {
args.value_of("FILEMODE").unwrap(),
e
);
std::process::exit(1)
std::process::exit(ERROR_STATUS_CLI)
}
};
if args.occurrences_of("FILEMODE") > 0 && args.occurrences_of("DIRMODE") == 0 {
@ -190,7 +198,7 @@ impl Config {
args.value_of("DIRMODE").unwrap(),
e
);
std::process::exit(1)
std::process::exit(ERROR_STATUS_CLI)
}
};
}
@ -234,12 +242,12 @@ impl Config {
if args.occurrences_of("INPUT") != 0 {
error!("It doesn't make sense to set `--new` with a specified input file.");
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
}
let output = PathBuf::from(target_file);
if output.exists() {
error!("Output file {} already exists.", output.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
let format = match args
.value_of("TARGET_FORMAT")
@ -272,7 +280,7 @@ impl Config {
"Unrecognized format '{}'; use --target or a known extension to specify a format.",
output.display()
);
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
}
}
}
@ -282,19 +290,23 @@ impl Config {
let mount_point = PathBuf::from(mount_point);
if !mount_point.exists() {
error!("Mount point {} does not exist.", mount_point.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
config.cleanup_mount = false;
Some(mount_point)
}
None => {
// If the output is to a file foo.EXT, then try to make a directory foo.
let mount_dir = output.with_extension("");
let stem = output.file_stem().unwrap_or_else(|| {
error!("Couldn't infer the mountpoint from output '{}'. Use `--mount MOUNT` to specify a mountpoint.", output.display());
std::process::exit(ERROR_STATUS_FUSE);
});
let mount_dir = PathBuf::from(stem);
// If that file already exists, give up and tell the user about --mount.
if mount_dir.exists() {
error!("Inferred mountpoint '{mount}' for output file '{file}', but '{mount}' already exists. Use `--mount MOUNT` to specify a mountpoint.",
mount = mount_dir.display(), file = output.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
// If the mountpoint can't be created, give up and tell the user about --mount.
if let Err(e) = std::fs::create_dir(&mount_dir) {
@ -302,7 +314,7 @@ impl Config {
mount_dir.display(),
e
);
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
// We did it!
config.cleanup_mount = true;
@ -327,7 +339,7 @@ impl Config {
let input_source = PathBuf::from(input_source);
if !input_source.exists() {
error!("Input file {} does not exist.", input_source.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
Input::File(input_source)
}
@ -366,7 +378,7 @@ impl Config {
let mount_point = PathBuf::from(mount_point);
if !mount_point.exists() {
error!("Mount point {} does not exist.", mount_point.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
config.cleanup_mount = false;
Some(mount_point)
@ -375,22 +387,28 @@ impl Config {
match &config.input {
Input::Stdin => {
error!("You must specify a mount point when reading from stdin.");
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
}
Input::Empty => {
error!(
"You must specify a mount point when reading an empty file."
);
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
}
Input::File(file) => {
// If the input is from a file foo.EXT, then try to make a directory foo.
let mount_dir = file.with_extension("");
let stem = file.file_stem().unwrap_or_else(|| {
error!("Couldn't infer the mountpoint from input '{}'. Use `--mount MOUNT` to specify a mountpoint.", file.display());
std::process::exit(ERROR_STATUS_FUSE);
});
let mount_dir = PathBuf::from(stem);
debug!("inferred mount_dir {}", mount_dir.display());
// If that file already exists, give up and tell the user about --mount.
if mount_dir.exists() {
error!("Inferred mountpoint '{mount}' for input file '{file}', but '{mount}' already exists. Use `--mount MOUNT` to specify a mountpoint.",
mount = mount_dir.display(), file = file.display());
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
// If the mountpoint can't be created, give up and tell the user about --mount.
if let Err(e) = std::fs::create_dir(&mount_dir) {
@ -399,7 +417,7 @@ impl Config {
mount_dir.display(),
e
);
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
// We did it!
config.cleanup_mount = true;

View File

@ -6,7 +6,7 @@ use tracing::{debug, error, info, instrument, warn};
use fuser::FileType;
use super::config::{Config, Input, Munge, Output};
use super::config::{ERROR_STATUS_FUSE, Config, Input, Munge, Output};
use super::fs::{DirEntry, DirType, Entry, Inode, FS};
use ::toml as serde_toml;
@ -141,7 +141,7 @@ impl Format {
let fmt = config.input_format;
let file = std::fs::File::open(&file).unwrap_or_else(|e| {
error!("Unable to open {} for {} input: {}", file.display(), fmt, e);
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
});
Box::new(file)
}
@ -311,7 +311,7 @@ where
if v.kind() != FileType::Directory {
error!("The root of the filesystem must be a directory, but '{}' only generates a single file.", v);
std::process::exit(1);
std::process::exit(ERROR_STATUS_FUSE);
}
let mut filtered = 0;

View File

@ -5,7 +5,7 @@ mod config;
mod format;
mod fs;
use config::Config;
use config::{Config, ERROR_STATUS_CLI, ERROR_STATUS_FUSE};
use fuser::MountOption;
@ -26,7 +26,7 @@ fn main() {
error!(
"No mount point specified; aborting. Use `--mount MOUNT` to specify a mountpoint."
);
std::process::exit(1);
std::process::exit(ERROR_STATUS_CLI);
}
};
let cleanup_mount = config.cleanup_mount;
@ -41,7 +41,7 @@ fn main() {
}
Err(e) => {
error!("I/O error: {}", e);
2
ERROR_STATUS_FUSE
}
};

178
tests/exit_status.sh Executable file
View File

@ -0,0 +1,178 @@
#!/bin/sh
#
# from https://github.com/mgree/ffs/issues/42
fail() {
echo FAILED: $1
if [ "$MNT" ]
then
umount "$D"/single
rm -r "$D"
fi
exit 1
}
TESTS="$(pwd)"
D=$(mktemp -d)
cp ../json/single.json "$D"/single.json
cp ../json/false.json "$D"/false.json
cd "$D"
cp single.json unreadable.json
chmod -r unreadable.json
mkdir unwriteable
chmod -w unwriteable
# in place mount, mountpoint exists
mkdir single
"$TESTS"/timeout -t 2 ffs -i single.json 2>single.err
[ $? -eq 1 ] || fail imountstatus
[ -s single.err ] || fail imountmsg
rmdir single
rm single.err
# in place, can't make mountpoint
cd unwriteable
"$TESTS"/timeout -t 2 ffs -i single.json 2>../single.err
[ $? -eq 1 ] || fail imkmountstatus
cd ..
[ -s single.err ] || fail imkmountmsg
rm single.err
# new, mountpoint exists
mkdir foo
"$TESTS"/timeout -t 2 ffs --new foo.json 2>foo.err
[ $? -eq 1 ] || fail newmountstatus
[ -s foo.err ] || fail newmountmsg
rmdir foo
rm foo.err
# new, can't make mountpoint
cd unwriteable
"$TESTS"/timeout -t 2 ffs --new foo.json 2>../foo.err
[ $? -eq 1 ] || fail newmkmountstatus
cd ..
[ -s foo.err ] || fail newmkmountmsg
rm foo.err
# input file, can't infer mountpoint
"$TESTS"/timeout -t 2 ffs --new .. 2>dotdot.err
[ $? -eq 1 ] || fail newdotdotmountstatus
[ -s dotdot.err ] || fail newdotdotmountmsg
rm dotdot.err
# --new, output file exists
touch foo.yaml
"$TESTS"/timeout -t 2 ffs --new foo.yaml 2>foo.err
[ $? -eq 1 ] || fail omountstatus
[ -s foo.err ] || fail omountmsg
rm foo.yaml
rm foo.err
# --new, mountpoint doesn't exist
"$TESTS"/timeout -t 2 ffs -m notthere --new foo.json 2>foo.err
[ $? -eq 1 ] || fail mmountstatus1
[ -s foo.err ] || fail mmountmsg1
rm foo.err
# mountpoint doesn't exist
"$TESTS"/timeout -t 2 ffs -m notthere single.json 2>single.err
[ $? -eq 1 ] || fail mmountstatus2
[ -s single.err ] || fail mmountmsg2
rm single.err
# input file doesn't exists
"$TESTS"/timeout -t 2 ffs nonesuch.toml 2>nonesuch.err
[ $? -eq 1 ] || fail inputmountstatus1
[ -s nonesuch.err ] || fail inputmountmsg1
rm nonesuch.err
# input file, mountpoint exists
mkdir single
"$TESTS"/timeout -t 2 ffs single.json 2>single.err
[ $? -eq 1 ] || fail inputmountstatus2
[ -s single.err ] || fail inputmountmsg2
rmdir single
rm single.err
# input file, can't make mount point
cd unwriteable
"$TESTS"/timeout -t 2 ffs ../single.json 2>../single.err
[ $? -eq 1 ] || fail inputmkmountstatus
cd ..
[ -s single.err ] || fail inputmkmountmsg
rm single.err
# input file, can't infer mountpoint
"$TESTS"/timeout -t 2 ffs .. 2>dotdot.err
[ $? -eq 1 ] || fail inputdotdotmountstatus
[ -s dotdot.err ] || fail inputdotdotmountmsg
rm dotdot.err
# unreadable input
"$TESTS"/timeout -t 2 ffs unreadable.json 2>ur.err
[ $? -eq 1 ] || fail unreadablemountstatus
[ -s ur.err ] || fail unreadablemountmsg
rm ur.err
# plain value input
"$TESTS"/timeout -t 2 ffs false.json 2>false.err
[ $? -eq 1 ] || fail falsemountstatus
[ -s false.err ] || fail falsemountmsg
rm false.err
# bad mount point (fuser is masking this error)
# "$TESTS"/timeout -t 2 ffs /etc single.json 2>etc.err
# [ $? -eq 1 ] || fail etcmountstatus
# [ -s etc.err ] || fail etcmountmsg
# rm etc.err
# bad shell completion
"$TESTS"/timeout -t 2 ffs --completions smoosh 2>comp.err
[ $? -eq 2 ] || fail compmountstatus
[ -s comp.err ] || fail compmountmsg
rm comp.err
# bad mode
"$TESTS"/timeout -t 2 ffs --mode 888 2>mode.err
[ $? -eq 2 ] || fail modemountstatus
[ -s mode.err ] || fail modemountmsg
rm mode.err
# bad dirmode
"$TESTS"/timeout -t 2 ffs --dirmode 888 2>dirmode.err
[ $? -eq 2 ] || fail dirmodemountstatus
[ -s dirmode.err ] || fail dirmodemountmsg
rm dirmode.err
# new and input file
"$TESTS"/timeout -t 2 ffs --new foo.json single.json 2>ni.err
[ $? -eq 2 ] || fail nimountstatus
[ -s ni.err ] || fail nimountmsg
rm ni.err
# unknown --source
"$TESTS"/timeout -t 2 ffs --source hieratic single.json 2>source.err
[ $? -eq 2 ] || fail sourcemountstatus
[ -s source.err ] || fail sourcemountmsg
rm source.err
# unknown --target
"$TESTS"/timeout -t 2 ffs --target hieratic single.json 2>target.err
[ $? -eq 2 ] || fail targetmountstatus
[ -s target.err ] || fail targetmountmsg
rm target.err
# stdin read, no mountpoint
"$TESTS"/timeout -t 2 ffs 2>im.err
[ $? -eq 2 ] || fail immountstatus
[ -s im.err ] || fail immountmsg
rm im.err
chmod +w unwriteable
cd "$TESTS"
rm -r "$D" || fail cleanup

39
tests/infer_mount_relative.sh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/sh
fail() {
echo FAILED: $1
if [ "$MNT" ]
then
cd
umount "$TMP"/object
rm -r "$TMP"
fi
exit 1
}
TMP=$(mktemp -d)
cp ../json/object.json "$TMP"
mkdir "$TMP"/nested
cd "$TMP"/nested
ffs ../object.json &
PID=$!
sleep 2
[ -d "object" ] || fail mountdir
case $(ls object) in
(eyes*fingernails*human*name) ;;
(*) fail ls;;
esac
[ "$(cat object/name)" = "Michael Greenberg" ] || fail name
[ "$(cat object/eyes)" -eq 2 ] || fail eyes
[ "$(cat object/fingernails)" -eq 10 ] || fail fingernails
[ "$(cat object/human)" = "true" ] || fail human
umount object || fail unmount
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process
[ -d "object" ] && fail cleanup
cd -
rm -r "$TMP"

View File

@ -5,22 +5,21 @@ fail() {
if [ "$MNT" ]
then
umount "$MNT"
rmdir "$MNT"
rm "$OUT" "$EXP"
rm -r "$D"
fi
exit 1
}
# really, just for the name
OUT=$(mktemp)
rm "$OUT"
MNT="$OUT"
OUT="$OUT".json
D=$(mktemp -d)
MNT=foo
OUT=foo.json
EXP=$(mktemp)
printf '{"handles":{"github":"mgree","stevens":"mgreenbe","twitter":"mgrnbrg"},"problems":99}' >"$EXP"
cd "$D"
ffs --new "$OUT" &
PID=$!
sleep 2
@ -40,4 +39,4 @@ kill -0 $PID >/dev/null 2>&1 && fail process
diff "$OUT" "$EXP" || fail diff
[ -e "$MNT" ] && fail mount
rm "$OUT" "$EXP"
rm -r "$D"

View File

@ -40,7 +40,7 @@ NESTEDSTATUS=$?
[ -f single.timeout ] && fail timeout
[ -s single.err ] || fail error
rm single.err
[ $NESTEDSTATUS -eq 2 ] || fail status
[ $NESTEDSTATUS -eq 1 ] || fail status
case $(ls) in
(onlyone*single.json) ;;