1
1
mirror of https://github.com/mgree/ffs.git synced 2024-07-14 23:00:24 +03:00

Better mount control (issue #12; pr #24)

Mountpoints are typically inferred/generated from filenames with a -m/--mount flag for when reading from STDIN or to manually specify a mountpoint.
This commit is contained in:
Michael Greenberg 2021-06-24 07:06:38 -07:00 committed by GitHub
parent 4639d31cc6
commit 4aff907523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 233 additions and 85 deletions

View File

@ -16,7 +16,25 @@ pub struct Config {
pub base64: base64::Config,
pub try_decode_base64: bool,
pub read_only: bool,
pub input: Input,
pub output: Output,
pub mount: Option<PathBuf>,
pub cleanup_mount: bool,
}
#[derive(Debug)]
pub enum Input {
Stdin,
File(PathBuf),
}
impl std::fmt::Display for Input {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Input::Stdin => write!(f, "<stdin>"),
Input::File(file) => write!(f, "{}", file.display()),
}
}
}
#[derive(Debug)]
@ -68,7 +86,10 @@ impl Default for Config {
base64: base64::STANDARD,
try_decode_base64: false,
read_only: false,
input: Input::Stdin,
output: Output::Stdout,
mount: None,
cleanup_mount: false,
}
}
}

View File

@ -1,5 +1,4 @@
use std::path::Path;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use clap::{App, Arg};
@ -11,7 +10,7 @@ mod config;
mod format;
mod fs;
use config::{Config, Output};
use config::{Config, Input, Output};
use format::Format;
use fuser::MountOption;
@ -121,15 +120,16 @@ fn main() {
)
.arg(
Arg::with_name("MOUNT")
.help("Sets the mountpoint")
.required(true)
.index(1),
.help("Sets the mountpoint; will be inferred when using a file, but must be specified when running on stdin")
.long("mount")
.short("m")
.takes_value(true)
)
.arg(
Arg::with_name("INPUT")
.help("Sets the input file (defaults to '-', meaning STDIN)")
.default_value("-")
.index(2),
.index(1),
)
.get_matches();
@ -193,13 +193,6 @@ fn main() {
};
}
// TODO 2021-06-08 infer and create mountpoint from filename as possible
let mount_point = Path::new(args.value_of("MOUNT").expect("mount point"));
if !mount_point.exists() {
error!("Mount point {} does not exist.", mount_point.display());
std::process::exit(1);
}
match args.value_of("UID") {
Some(uid_string) => match uid_string.parse() {
Ok(uid) => config.uid = uid,
@ -229,15 +222,32 @@ fn main() {
None => config.gid = unsafe { libc::getegid() },
}
let input_source = args.value_of("INPUT").expect("input source");
config.input = match args.value_of("INPUT") {
Some(input_source) => {
if input_source == "-" {
Input::Stdin
} else {
let input_source = PathBuf::from(input_source);
if !input_source.exists() {
error!("Input file {} does not exist.", input_source.display());
std::process::exit(1);
}
Input::File(input_source)
}
}
None => Input::Stdin,
};
config.output = if let Some(output) = args.value_of("OUTPUT") {
Output::File(PathBuf::from(output))
} else if args.is_present("INPLACE") {
if input_source == "-" {
warn!("In-place output `-i` with STDIN input makes no sense; outputting on STDOUT.");
Output::Stdout
} else {
Output::File(PathBuf::from(input_source))
match &config.input {
Input::Stdin => {
warn!(
"In-place output `-i` with STDIN input makes no sense; outputting on STDOUT."
);
Output::Stdout
}
Input::File(input_source) => Output::File(input_source.clone()),
}
} else if args.is_present("NOOUTPUT") || args.is_present("QUIET") {
Output::Quiet
@ -245,6 +255,54 @@ fn main() {
Output::Stdout
};
// infer and create mountpoint from filename as possible
config.mount = match args.value_of("MOUNT") {
Some(mount_point) => {
let mount_point = PathBuf::from(mount_point);
if !mount_point.exists() {
error!("Mount point {} does not exist.", mount_point.display());
std::process::exit(1);
}
config.cleanup_mount = false;
Some(mount_point)
}
None => {
match &config.input {
Input::Stdin => {
error!("You must specify a mount point when reading from stdin.");
std::process::exit(1);
}
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("");
// 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);
}
// 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) {
error!(
"Couldn't create mountpoint '{}': {}. Use `--mount MOUNT` to specify a mountpoint.",
mount_dir.display(),
e
);
std::process::exit(1);
}
// We did it!
config.cleanup_mount = true;
Some(mount_dir)
}
}
}
};
assert!(config.mount.is_some());
// try to autodetect the input format.
//
// first see if it's specified and parses okay.
@ -268,18 +326,23 @@ fn main() {
}
};
match Path::new(input_source)
.extension() // will fail for STDIN, no worries
.map(|s| s.to_str().expect("utf8 filename").to_lowercase())
{
Some(s) => match s.parse::<Format>() {
match &config.input {
Input::Stdin => Format::Json,
Input::File(input_source) => match input_source
.extension()
.and_then(|s| s.to_str())
.ok_or(format::ParseFormatError::NoFormatProvided)
.and_then(|s| s.parse::<Format>())
{
Ok(format) => format,
Err(_) => {
warn!("Unrecognized format {}, defaulting to JSON.", s);
warn!(
"Unrecognized format {}, defaulting to JSON.",
input_source.display()
);
Format::Json
}
},
None => Format::Json,
}
}
};
@ -330,7 +393,11 @@ fn main() {
}
};
let mut options = vec![MountOption::FSName(input_source.into())];
////////////////////////////////////////////////////////////////////////////
// DONE PARSING
////////////////////////////////////////////////////////////////////////////
let mut options = vec![MountOption::FSName(format!("{}", config.input))];
if args.is_present("AUTOUNMOUNT") {
options.push(MountOption::AutoUnmount);
}
@ -338,19 +405,45 @@ fn main() {
options.push(MountOption::RO);
}
let input_format = config.input_format;
let reader: Box<dyn std::io::Read> = if input_source == "-" {
Box::new(std::io::stdin())
} else {
let file = std::fs::File::open(input_source).unwrap_or_else(|e| {
error!("Unable to open {} for JSON input: {}", input_source, e);
assert!(config.mount.is_some());
let mount = match &config.mount {
Some(mount) => mount.clone(),
None => {
error!(
"No mount point specified; aborting. Use `--mount MOUNT` to specify a mountpoint."
);
std::process::exit(1);
});
Box::new(file)
}
};
let cleanup_mount = config.cleanup_mount;
let input_format = config.input_format;
let reader: Box<dyn std::io::Read> = match &config.input {
Input::Stdin => Box::new(std::io::stdin()),
Input::File(file) => {
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);
});
Box::new(file)
}
};
let fs = input_format.load(reader, config);
info!("mounting on {:?} with options {:?}", mount_point, options);
fuser::mount2(fs, mount_point, &options).unwrap();
info!("mounting on {:?} with options {:?}", mount, options);
fuser::mount2(fs, &mount, &options).unwrap();
info!("unmounted");
if cleanup_mount {
if mount.exists() {
if let Err(e) = std::fs::remove_dir(&mount) {
warn!("Unable to clean up mountpoint '{}': {}", mount.display(), e);
}
} else {
warn!(
"Mountpoint '{}' disappeared before ffs could cleanup.",
mount.display()
);
}
}
}

View File

@ -15,7 +15,7 @@ MNT=$(mktemp -d)
OUT=$(mktemp)
MSG=$(mktemp)
ffs "$MNT" ../json/null.json >"$OUT" 2>"$MSG" &
ffs -m "$MNT" ../json/null.json >"$OUT" 2>"$MSG" &
PID=$!
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process

View File

@ -15,7 +15,7 @@ MNT=$(mktemp -d)
OUT=$(mktemp)
MSG=$(mktemp)
echo \"just a string\" | ffs "$MNT" >"$OUT" 2>"$MSG" &
echo \"just a string\" | ffs -m "$MNT" >"$OUT" 2>"$MSG" &
PID=$!
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/list.json &
ffs -m "$MNT" ../json/list.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/object.json &
ffs -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -22,7 +22,7 @@ printf "10" >"${EXP}/fingernails"
printf "true" >"${EXP}/human"
printf "" >"${EXP}/problems"
ffs "$MNT" ../json/object_null.json &
ffs -m "$MNT" ../json/object_null.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -22,7 +22,7 @@ printf "10\n" >"${EXP}/fingernails"
printf "true\n" >"${EXP}/human"
printf "" >"${EXP}/problems"
ffs --newline "$MNT" ../json/object_null.json &
ffs --newline -m "$MNT" ../json/object_null.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
cat ../json/object.json | ffs "$MNT" &
cat ../json/object.json | ffs -m "$MNT" &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../toml/eg.toml &
ffs -m "$MNT" ../toml/eg.toml &
PID=$!
sleep 2
case $(ls "$MNT") in

View File

@ -30,7 +30,7 @@ MNT=$(mktemp -d)
TGT=$(mktemp)
TGT2=$(mktemp)
ffs "$MNT" ../json/object.json >"$TGT" &
ffs -m "$MNT" ../json/object.json >"$TGT" &
PID=$!
sleep 2
cp ../binary/twitter.ico "$MNT"/favicon
@ -42,7 +42,7 @@ kill -0 $PID >/dev/null 2>&1 && fail process1
[ -f "$TGT" ] || fail output1
[ -s "$TGT" ] || fail output2
grep favicon "$TGT" >/dev/null 2>&1 || fail text
ffs --no-output "$MNT" "$TGT" >"$TGT2" &
ffs --no-output -m "$MNT" "$TGT" >"$TGT2" &
PID=$!
sleep 2

View File

@ -17,7 +17,7 @@ MNT=$(mktemp -d)
TGT=$(mktemp)
TGT2=$(mktemp)
ffs "$MNT" ../json/object.json >"$TGT" &
ffs -m "$MNT" ../json/object.json >"$TGT" &
PID=$!
sleep 2
echo 'echo hi' >"$MNT"/script
@ -31,7 +31,7 @@ kill -0 $PID >/dev/null 2>&1 && fail process1
[ -f "$TGT" ] || fail output1
[ -s "$TGT" ] || fail output2
grep -e echo "$TGT" >/dev/null 2>&1 || fail grep
ffs --no-output --source json "$MNT" "$TGT" >"$TGT2" &
ffs --no-output --source json -m "$MNT" "$TGT" >"$TGT2" &
PID=$!
sleep 2

View File

@ -15,14 +15,11 @@ fail() {
MNT=$(mktemp -d)
ERR=$(mktemp)
RUST_LOG="ffs=debug" ffs -d --no-output "$MNT" ../json/object.json &
ffs -d --no-output -m "$MNT" ../json/object.json &
PID=$!
sleep 2
chown :nobody "$MNT"/name 2>$ERR >&2 && fail "chgrp1: $(cat $ERR)"
[ -s "$ERR" ] || fail "chgrp1 error: $(cat $ERR)"
groups
ls -l "$MNT"/name
echo $(groups | cut -d' ' -f 1)
chown :$(groups | cut -d' ' -f 1) "$MNT"/name 2>$ERR >&2 || fail "chgrp2: $(cat $ERR)"
[ -s "$ERR" ] && fail "chgrp2 error: $(cat $ERR)"
chown $(whoami) "$MNT"/name 2>$ERR >&2 || fail chown

View File

@ -25,7 +25,7 @@ printf "true" >"${EXP}/human"
printf "hi\n" >"${EXP}/greeting"
printf "bye" >"${EXP}/farewell"
ffs -o "$JSON" "$MNT" ../json/object.json &
ffs -o "$JSON" -m "$MNT" ../json/object.json &
PID=$!
sleep 2
echo hi >"$MNT"/greeting
@ -35,7 +35,7 @@ sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process
# remount w/o --newline, confirm that they're not there (except for greeting)
ffs "$MNT" "$JSON" &
ffs -m "$MNT" "$JSON" &
sleep 2
case $(ls "$MNT") in
(eyes*farewell*fingernails*greeting*human*name) ;;

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/object.json &
ffs -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs --uid $(id -u root) --gid $(id -g root) "$MNT" ../json/object.json &
ffs --uid $(id -u root) --gid $(id -g root) -m "$MNT" ../json/object.json &
PID=$!
sleep 2
ls -l "$MNT" | grep root >/dev/null 2>&1 || fail user

37
tests/infer_mount.sh Executable file
View File

@ -0,0 +1,37 @@
#!/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"
cd "$TMP"
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

@ -15,7 +15,7 @@ fail() {
MNT=$(mktemp -d)
TGT=$(mktemp)
ffs --source json --target toml -o "$TGT" "$MNT" ../json/single.json &
ffs --source json --target toml -o "$TGT" -m "$MNT" ../json/single.json &
PID=$!
sleep 2
umount "$MNT" || fail unmount1

View File

@ -15,7 +15,7 @@ MNT=$(mktemp -d)
umask 022
ffs --mode 666 "$MNT" ../json/object.json &
ffs --mode 666 -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"
@ -28,7 +28,7 @@ sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process1
umask 077
ffs --mode 666 --dirmode 700 "$MNT" ../json/object.json &
ffs --mode 666 --dirmode 700 -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -25,7 +25,7 @@ printf "true" >"${EXP}/human"
printf "hi" >"${EXP}/greeting"
printf "bye" >"${EXP}/farewell"
ffs --newline -o "$JSON" "$MNT" ../json/object.json &
ffs --newline -o "$JSON" -m "$MNT" ../json/object.json &
PID=$!
sleep 2
echo hi >"$MNT"/greeting
@ -35,7 +35,7 @@ sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process
# remount w/o --newline, confirm that they're not there
ffs "$MNT" "$JSON" &
ffs -m "$MNT" "$JSON" &
sleep 2
case $(ls "$MNT") in
(eyes*farewell*fingernails*greeting*human*name) ;;

View File

@ -25,7 +25,7 @@ fi
MNT=$(mktemp -d)
ffs "$MNT" ../json/nlink.json &
ffs -m "$MNT" ../json/nlink.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -17,7 +17,7 @@ MNT=$(mktemp -d)
TGT=$(mktemp)
TGT2=$(mktemp)
ffs "$MNT" ../json/object.json >"$TGT" &
ffs -m "$MNT" ../json/object.json >"$TGT" &
PID=$!
sleep 2
mkdir "$MNT"/pockets
@ -38,7 +38,7 @@ kill -0 $PID >/dev/null 2>&1 && fail process1
[ -s "$TGT" ] || fail output2
cat "$TGT"
stat "$TGT"
ffs --no-output "$MNT" "$TGT" >"$TGT2" &
ffs --no-output -m "$MNT" "$TGT" >"$TGT2" &
PID=$!
sleep 2

View File

@ -18,7 +18,7 @@ TGT=$(mktemp)
cp ../toml/single.toml "$SRC"
ffs --source toml --target json -o "$TGT" "$MNT" "$SRC" &
ffs --source toml --target json -o "$TGT" -m "$MNT" "$SRC" &
PID=$!
sleep 2
umount "$MNT" || fail unmount1

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/list2.json &
ffs -m "$MNT" ../json/list2.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -16,7 +16,7 @@ JSON=$(mktemp)
cp ../json/object.json "$JSON"
ffs -qi "$MNT" "$JSON" &
ffs -qi -m "$MNT" "$JSON" &
PID=$!
sleep 2
echo hi >"$MNT"/greeting
@ -27,7 +27,7 @@ kill -0 $PID >/dev/null 2>&1 && fail process1
diff ../json/object.json "$JSON" >/dev/null && fail same
ffs --readonly "$MNT" "$JSON" &
ffs --readonly -m "$MNT" "$JSON" &
PID=$!
sleep 2
[ "$(cat $MNT/greeting)" = "hi" ] || fail updated

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs --readonly "$MNT" ../json/object.json &
ffs --readonly -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/object.json &
ffs -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/obj_rename.json &
ffs -m "$MNT" ../json/obj_rename.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/object.json &
ffs -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -20,7 +20,7 @@ TOML="$TOML".toml
cp ../toml/eg.toml "$TOML"
ffs -i "$MNT" "$TOML" &
ffs -i -m "$MNT" "$TOML" &
PID=$!
sleep 2
case $(ls "$MNT") in
@ -35,7 +35,7 @@ umount "$MNT" || fail unmount1
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process1
ffs --readonly --no-output "$MNT" "$TOML" &
ffs --readonly --no-output -m "$MNT" "$TOML" &
PID=$!
sleep 2
[ "$(cat $MNT/clients/hosts/0)" = "alpha" ] || fail hosts0

View File

@ -15,7 +15,7 @@ fail() {
MNT=$(mktemp -d)
TGT=$(mktemp)
ffs --source toml --target json -o "$TGT" "$MNT" ../toml/single.toml &
ffs --source toml --target json -o "$TGT" -m "$MNT" ../toml/single.toml &
PID=$!
sleep 2
umount "$MNT" || fail unmount1

View File

@ -15,7 +15,7 @@ fail() {
MNT=$(mktemp -d)
ERR=$(mktemp)
ffs --no-output "$MNT" ../json/object.json &
ffs --no-output -m "$MNT" ../json/object.json &
PID=$!
sleep 2
touch "$MNT"/name 2>$ERR >&2 || { cat "$ERR"; fail touch; }

View File

@ -19,7 +19,7 @@ TGT=$(mktemp)
TGT2=$(mktemp)
ERR=$(mktemp)
ffs "$MNT" ../json/object.json >"$TGT" &
ffs -m "$MNT" ../json/object.json >"$TGT" &
PID=$!
sleep 2
echo 'Mikey Indiana' >"$MNT"/name 2>"$ERR"
@ -32,7 +32,7 @@ kill -0 $PID >/dev/null 2>&1 && fail process1
[ -f "$TGT" ] || fail output1
[ -s "$TGT" ] || fail output2
grep -e Indiana "$TGT" >/dev/null 2>&1 || fail grep
ffs --no-output --source json "$MNT" "$TGT" >"$TGT2" &
ffs --no-output --source json -m "$MNT" "$TGT" >"$TGT2" &
PID=$!
sleep 2

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs "$MNT" ../json/object.json &
ffs -m "$MNT" ../json/object.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -13,7 +13,7 @@ fail() {
MNT=$(mktemp -d)
ffs --unpadded "$MNT" ../json/list2.json &
ffs --unpadded -m "$MNT" ../json/list2.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -20,7 +20,7 @@ hi
hello
EOF
ffs "$MNT" ../json/list.json &
ffs -m "$MNT" ../json/list.json &
PID=$!
sleep 2
cd "$MNT"

View File

@ -20,7 +20,7 @@ YAML="$YAML".yaml
cp ../yaml/invoice.yaml "$YAML"
ffs -i "$MNT" "$YAML" &
ffs -i -m "$MNT" "$YAML" &
PID=$!
sleep 2
case $(ls "$MNT") in
@ -35,7 +35,7 @@ umount "$MNT" || fail unmount1
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process1
ffs --readonly --no-output "$MNT" "$YAML" &
ffs --readonly --no-output -m "$MNT" "$YAML" &
PID=$!
sleep 2
[ "$(cat $MNT/product/0/description)" = "Basketball" ] || fail desc1