Skip to content

Commit

Permalink
Basics of output seem to be working.
Browse files Browse the repository at this point in the history
Addresses (most of) #5.
  • Loading branch information
mgree committed Jun 16, 2021
1 parent 3b3677f commit ba64e76
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 15 deletions.
11 changes: 11 additions & 0 deletions src/config.rs
@@ -1,3 +1,5 @@
use std::path::PathBuf;

#[derive(Debug)]
pub struct Config {
pub timestamp: std::time::SystemTime,
Expand All @@ -8,6 +10,14 @@ pub struct Config {
pub add_newlines: bool,
pub pad_element_names: bool,
pub read_only: bool,
pub output: Output,
}

#[derive(Debug)]
pub enum Output {
Quiet,
Stdout,
File(PathBuf),
}

impl Config {
Expand Down Expand Up @@ -41,6 +51,7 @@ impl Default for Config {
add_newlines: false,
pad_element_names: true,
read_only: false,
output: Output::Stdout,
}
}
}
32 changes: 25 additions & 7 deletions src/fs.rs
@@ -1,6 +1,6 @@
use fuser::ReplyIoctl;
use fuser::ReplyCreate;
use fuser::ReplyEmpty;
use fuser::ReplyIoctl;
use fuser::ReplyWrite;
use std::collections::HashMap;
use std::ffi::OsStr;
Expand All @@ -10,10 +10,12 @@ use fuser::{
FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request,
};

use tracing::{debug, warn};
use tracing::{debug, instrument, warn};

use super::config::Config;

use super::json;

/// A filesystem `FS` is just a vector of nullable inodes, where the index is
/// the inode number.
///
Expand Down Expand Up @@ -80,7 +82,7 @@ impl FS {
req.uid() == self.config.uid
}

fn get(&self, inum: u64) -> Result<&Inode, FSError> {
pub fn get(&self, inum: u64) -> Result<&Inode, FSError> {
let idx = inum as usize;

if idx >= self.inodes.len() {
Expand Down Expand Up @@ -148,6 +150,16 @@ impl FS {
flags: 0, // weird macOS thing
}
}

/// Syncs the FS with its on-disk representation
///
/// TODO 2021-06-16 need some reference to the output format to do the right thing
#[instrument(level = "debug", skip(self))]
pub fn sync(&self) {
debug!("{:?}", self.inodes);

json::save_fs(self);
}
}

impl Entry {
Expand All @@ -171,7 +183,7 @@ impl Entry {

impl Filesystem for FS {
fn destroy(&mut self, _req: &Request) {
debug!("{:?}", self.inodes);
self.sync();
}

fn access(&mut self, req: &Request, inode: u64, mut mask: i32, reply: ReplyEmpty) {
Expand Down Expand Up @@ -839,8 +851,14 @@ impl Filesystem for FS {

// load the contents
let contents = match self.get_mut(ino) {
Ok(Inode { entry: Entry::File(contents), .. }) => contents,
Ok(Inode { entry: Entry::Directory(..), .. }) => {
Ok(Inode {
entry: Entry::File(contents),
..
}) => contents,
Ok(Inode {
entry: Entry::Directory(..),
..
}) => {
reply.error(libc::EBADF);
return;
}
Expand Down Expand Up @@ -871,7 +889,7 @@ impl Filesystem for FS {
_offset_out: i64,
_len: u64,
_flags: u32,
reply: ReplyWrite
reply: ReplyWrite,
) {
reply.error(libc::ENOSYS);
}
Expand Down
81 changes: 77 additions & 4 deletions src/json.rs
@@ -1,12 +1,14 @@
use std::collections::HashMap;
use std::fs::File;
use std::str::FromStr;

use serde_json::Value;

use tracing::{info, instrument};
use tracing::{debug, info, instrument};

use fuser::FileType;

use super::config::Config;
use super::config::{Config, Output};
use super::fs::{DirEntry, DirType, Entry, Inode, FS};

/// Parses JSON into a value; just a shim for `serde_json::from_reader`.
Expand Down Expand Up @@ -44,7 +46,7 @@ fn size(v: &Value) -> usize {
/// Invariant: the index in the vector is the inode number. Inode 0 is invalid,
/// and is left empty.
#[instrument(level = "info", skip(v, config))]
pub fn fs(config: Config, v: Value) -> FS {
pub fn load_fs(config: Config, v: Value) -> FS {
let mut inodes: Vec<Option<Inode>> = Vec::new();

// reserve space for everyone else
Expand Down Expand Up @@ -76,7 +78,7 @@ pub fn fs(config: Config, v: Value) -> FS {
Value::String(s) => {
let contents = if s.ends_with('\n') { s } else { s + nl };
Entry::File(contents.into_bytes())
},
}
Value::Array(vs) => {
let mut children = HashMap::new();
children.reserve(vs.len());
Expand Down Expand Up @@ -150,3 +152,74 @@ pub fn fs(config: Config, v: Value) -> FS {

FS { inodes, config }
}

#[instrument(level = "info", skip(fs))]
pub fn save_fs(fs: &FS) {
let writer : Box<dyn std::io::Write> = match &fs.config.output {
Output::Stdout => {
Box::new(std::io::stdout())
}
Output::File(path) => {
debug!("output {}", path.display());
Box::new(File::create(path).unwrap())
}
Output::Quiet => {
debug!("no output path, skipping");
return;
}
};

let v = value_from_fs(fs, fuser::FUSE_ROOT_ID);
serde_json::to_writer(writer, &v).unwrap();
}

pub fn value_from_fs(fs: &FS, inum: u64) -> Value {
match &fs.get(inum).unwrap().entry {
Entry::File(contents) => {
// TODO 2021-06-16 better newline handling
let contents = match String::from_utf8(contents.clone()) {
Ok(mut contents) => {
if contents.ends_with('\n') {
contents.truncate(contents.len() - 1);
}
contents
}
Err(_) => unimplemented!("binary data JSON serialization"),
};

if contents.is_empty() {
Value::Null
} else if contents == "true" {
Value::Bool(true)
} else if contents == "false" {
Value::Bool(false)
} else if let Ok(n) = serde_json::Number::from_str(&contents) {
Value::Number(n)
} else {
Value::String(contents)
}
}
Entry::Directory(DirType::List, files) => {
let mut entries = Vec::with_capacity(files.len());

let mut files = files.iter().collect::<Vec<_>>();
files.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2));
for (_name, DirEntry { inum, .. }) in files.iter() {
let v = value_from_fs(fs, *inum);
entries.push(v);
}

Value::Array(entries)
}
Entry::Directory(DirType::Named, files) => {
let mut entries = serde_json::map::Map::new();

for (name, DirEntry { inum, .. }) in files.iter() {
let v = value_from_fs(fs, *inum);
entries.insert(name.into(), v);
}

Value::Object(entries)
}
}
}
44 changes: 40 additions & 4 deletions src/main.rs
@@ -1,4 +1,5 @@
use std::path::Path;
use std::path::PathBuf;

use clap::{App, Arg};

Expand All @@ -10,7 +11,7 @@ mod config;
mod fs;
mod json;

use config::Config;
use config::{Config, Output};

use fuser::MountOption;

Expand Down Expand Up @@ -67,6 +68,27 @@ fn main() {
.help("Mounted filesystem will be readonly")
.long("readonly")
)
.arg(
Arg::with_name("OUTPUT")
.help("Sets the output file for saving changes (defaults to stdout)")
.long("output")
.short("o")
.takes_value(true)
)
.arg(
Arg::with_name("NOOUTPUT")
.help("Disables output")
.long("no-output")
.overrides_with("OUTPUT")
)
.arg(
Arg::with_name("INPLACE")
.help("Writes the output back over the input file")
.long("in-place")
.short("i")
.overrides_with("OUTPUT")
.overrides_with("NOOUTPUT")
)
.arg(
Arg::with_name("MOUNT")
.help("Sets the mountpoint")
Expand All @@ -84,7 +106,7 @@ fn main() {
let mut config = Config::default();

let filter_layer = LevelFilter::DEBUG;
let fmt_layer = fmt::layer();
let fmt_layer = fmt::layer().with_writer(std::io::stderr);
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
Expand Down Expand Up @@ -172,7 +194,21 @@ fn main() {
}

let input_source = args.value_of("INPUT").expect("input source");

config.output = if let Some(output) = args.value_of("OUTPUT") {
Output::File(PathBuf::from(output))
} else if args.is_present("NOOUTPUT") {
Output::Quiet
} 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))
}
} else {
Output::Stdout
};

let reader: Box<dyn std::io::BufRead> = if input_source == "-" {
Box::new(std::io::BufReader::new(std::io::stdin()))
} else {
Expand All @@ -192,7 +228,7 @@ fn main() {
}

let v = json::parse(reader);
let fs = json::fs(config, v);
let fs = json::load_fs(config, v);

info!("mounting on {:?} with options {:?}", mount_point, options);
fuser::mount2(fs, mount_point, &options).unwrap();
Expand Down
61 changes: 61 additions & 0 deletions tests/output.sh
@@ -0,0 +1,61 @@
#!/bin/sh

fail() {
echo FAILED: $1
if [ "$MNT" ]
then
cd
umount "$MNT"
rmdir "$MNT"
rm "$TGT"
rm "$TGT2"
fi
exit 1
}

MNT=$(mktemp -d)
TGT=$(mktemp)
TGT2=$(mktemp)

ffs "$MNT" ../json/object.json >"$TGT" &
PID=$!
sleep 2
mkdir "$MNT"/pockets
echo keys >"$MNT"/pockets/pants
echo pen >"$MNT"/pockets/shirt
cd - >/dev/null 2>&1
umount "$MNT" || fail unmount1
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process1

# easiest to just test using ffs, but would be cool to get outside validation
[ -f "$TGT" ] || fail output1
cat "$TGT"
ffs --no-output "$MNT" "$TGT" >"$TGT2" &
PID=$!
sleep 2

case $(ls "$MNT") in
(eyes*fingernails*human*name*pockets) ;;
(*) fail ls1;;
esac
case $(ls "$MNT"/pockets) in
(pants*shirt) ;;
(*) fail ls2;;
esac

[ "$(cat $MNT/name)" = "Michael Greenberg" ] || fail name
[ "$(cat $MNT/eyes)" -eq 2 ] || fail eyes
[ "$(cat $MNT/fingernails)" -eq 10 ] || fail fingernails
[ "$(cat $MNT/human)" = "true" ] || fail human
[ "$(cat $MNT/pockets/pants)" = "keys" ] || fail pants
[ "$(cat $MNT/pockets/shirt)" = "pen" ] || fail shirt

umount "$MNT" || fail unmount2
sleep 1
kill -0 $PID >/dev/null 2>&1 && fail process2


rmdir "$MNT" || fail mount
rm "$TGT"
rm "$TGT2"

0 comments on commit ba64e76

Please sign in to comment.