mirror of
https://github.com/mgree/ffs.git
synced 2024-09-11 11:15:48 +03:00
More careful name munging (#34)
Save original names that don't work as filenames (e.g., `.` and `..`). We restore these names as appropriate---if a file is `rename`d with the _same_ name, we leave it alone. But renames to fresh names destroy original names. Resolves #29. There is no way to create a file with such a name: the metadata is purely copied. There's a TODO note in the code to allow users to inspect and edit this metadata, but it's not worth exposing until somebody asks for it. (I mean, really, please don't use `.` or `:/` as a property name.)
This commit is contained in:
parent
7357d88353
commit
726a175163
@ -3,6 +3,13 @@ use std::path::PathBuf;
|
||||
|
||||
use super::format::Format;
|
||||
|
||||
/// Configuration information
|
||||
///
|
||||
/// See `cli.rs` for information on the actual command-line options; see
|
||||
/// `main.rs` for how those connect to this structure.
|
||||
///
|
||||
/// NB I know this arrangement sucks, but `clap`'s automatic stuff isn't
|
||||
/// adequate to express what I want here. Command-line interfaces are hard. 😢
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub input_format: Format,
|
||||
@ -74,6 +81,12 @@ impl Config {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` for filenames that should not be serialized back.
|
||||
///
|
||||
/// By default, this includes `.` and `..` (though neither of these occur in
|
||||
/// `FS` as `Inode`s). On macOS, filenames starting with `._` are ignored,
|
||||
/// as well---these are where macOS will store extended attributes on
|
||||
/// filesystems that don't support them.
|
||||
pub fn ignored_file(&self, s: &str) -> bool {
|
||||
s == "." || s == ".." || self.platform_ignored_file(s)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::str::FromStr;
|
||||
|
||||
use tracing::{debug, error, warn, info, instrument};
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
|
||||
use fuser::FileType;
|
||||
|
||||
@ -298,8 +298,9 @@ where
|
||||
children.insert(
|
||||
name,
|
||||
DirEntry {
|
||||
inum: next_id,
|
||||
kind: child.kind(),
|
||||
original_name: None,
|
||||
inum: next_id,
|
||||
},
|
||||
);
|
||||
worklist.push((inum, next_id, child));
|
||||
@ -320,18 +321,22 @@ where
|
||||
nfield.push('_');
|
||||
}
|
||||
|
||||
if original != nfield {
|
||||
let original_name = if original != nfield {
|
||||
info!(
|
||||
"renamed {} to {} (inode {} with parent {})",
|
||||
original, nfield, next_id, parent
|
||||
);
|
||||
}
|
||||
Some(original)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
children.insert(
|
||||
nfield,
|
||||
DirEntry {
|
||||
inum: next_id,
|
||||
kind: child.kind(),
|
||||
original_name,
|
||||
inum: next_id,
|
||||
},
|
||||
);
|
||||
|
||||
@ -388,14 +393,24 @@ where
|
||||
Entry::Directory(DirType::Named, files) => {
|
||||
let mut entries = HashMap::with_capacity(files.len());
|
||||
|
||||
for (name, DirEntry { inum, .. }) in files.iter() {
|
||||
for (
|
||||
name,
|
||||
DirEntry {
|
||||
inum,
|
||||
original_name,
|
||||
..
|
||||
},
|
||||
) in files.iter()
|
||||
{
|
||||
if fs.config.ignored_file(name) {
|
||||
warn!("skipping ignored file '{}'", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let v = value_from_fs(fs, *inum);
|
||||
entries.insert(name.into(), v);
|
||||
|
||||
let name = original_name.as_ref().unwrap_or(name).into();
|
||||
entries.insert(name, v);
|
||||
}
|
||||
|
||||
V::from_named_dir(entries, &fs.config)
|
||||
|
56
src/fs.rs
56
src/fs.rs
@ -87,6 +87,11 @@ pub enum Entry {
|
||||
#[derive(Debug)]
|
||||
pub struct DirEntry {
|
||||
pub kind: FileType,
|
||||
/// When loading from certain map types, names might get munged.
|
||||
/// We store the original name here so we can restore it appropriately.
|
||||
///
|
||||
/// If the file is renamed, we'll drop the original name.
|
||||
pub original_name: Option<String>,
|
||||
pub inum: u64,
|
||||
}
|
||||
|
||||
@ -328,7 +333,13 @@ impl FromStr for DirType {
|
||||
|
||||
if s == "list" || s == "array" {
|
||||
Ok(DirType::List)
|
||||
} else if s == "named" || s == "object" || s == "map" || s == "hash" || s == "dict" || s == "dictionary" {
|
||||
} else if s == "named"
|
||||
|| s == "object"
|
||||
|| s == "map"
|
||||
|| s == "hash"
|
||||
|| s == "dict"
|
||||
|| s == "dictionary"
|
||||
{
|
||||
Ok(DirType::Named)
|
||||
} else {
|
||||
Err(())
|
||||
@ -748,6 +759,9 @@ impl Filesystem for FS {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO 2021-07-02
|
||||
// - we could add user.original_name here when present
|
||||
// - we could use a clearer name (e.g., `user.ffs.type`)
|
||||
let mut attrs: Vec<u8> = "user.type".into();
|
||||
attrs.push(0);
|
||||
let actual_size = attrs.len() as u32;
|
||||
@ -838,9 +852,9 @@ impl Filesystem for FS {
|
||||
(inode.parent, FileType::Directory, ".."),
|
||||
];
|
||||
|
||||
let entries = files
|
||||
.iter()
|
||||
.map(|(filename, DirEntry { inum, kind })| (*inum, *kind, filename.as_str()));
|
||||
let entries = files.iter().map(|(filename, DirEntry { inum, kind, .. })| {
|
||||
(*inum, *kind, filename.as_str())
|
||||
});
|
||||
|
||||
for (i, entry) in dot_entries
|
||||
.into_iter()
|
||||
@ -955,7 +969,14 @@ impl Filesystem for FS {
|
||||
Ok(inode) => match &mut inode.entry {
|
||||
Entry::File(..) => unreachable!("parent changed to a regular file"),
|
||||
Entry::Directory(_dirtype, files) => {
|
||||
files.insert(filename.into(), DirEntry { kind, inum });
|
||||
files.insert(
|
||||
filename.into(),
|
||||
DirEntry {
|
||||
kind,
|
||||
original_name: None,
|
||||
inum,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -1024,7 +1045,14 @@ impl Filesystem for FS {
|
||||
Ok(inode) => match &mut inode.entry {
|
||||
Entry::File(..) => unreachable!("parent changed to a regular file"),
|
||||
Entry::Directory(_dirtype, files) => {
|
||||
files.insert(filename.into(), DirEntry { kind, inum });
|
||||
files.insert(
|
||||
filename.into(),
|
||||
DirEntry {
|
||||
kind,
|
||||
original_name: None,
|
||||
inum,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -1188,6 +1216,7 @@ impl Filesystem for FS {
|
||||
Some(DirEntry {
|
||||
kind: FileType::Directory,
|
||||
inum,
|
||||
..
|
||||
}) => inum,
|
||||
Some(_) => {
|
||||
reply.error(libc::ENOTDIR);
|
||||
@ -1272,12 +1301,17 @@ impl Filesystem for FS {
|
||||
};
|
||||
|
||||
// make sure src exists
|
||||
let (src_kind, src_inum) = match self.get(parent) {
|
||||
let (src_kind, src_original, src_inum) = match self.get(parent) {
|
||||
Ok(Inode {
|
||||
entry: Entry::Directory(_kind, files),
|
||||
..
|
||||
}) => match files.get(src) {
|
||||
Some(DirEntry { kind, inum }) => (*kind, *inum),
|
||||
Some(DirEntry {
|
||||
kind,
|
||||
original_name,
|
||||
inum,
|
||||
..
|
||||
}) => (*kind, original_name.clone(), *inum),
|
||||
None => {
|
||||
reply.error(libc::ENOENT);
|
||||
return;
|
||||
@ -1294,7 +1328,7 @@ impl Filesystem for FS {
|
||||
entry: Entry::Directory(_kind, files),
|
||||
..
|
||||
}) => match files.get(tgt) {
|
||||
Some(DirEntry { kind, inum }) => {
|
||||
Some(DirEntry { kind, inum, .. }) => {
|
||||
if src_kind != *kind {
|
||||
reply.error(libc::ENOTDIR);
|
||||
return;
|
||||
@ -1342,6 +1376,10 @@ impl Filesystem for FS {
|
||||
tgt.into(),
|
||||
DirEntry {
|
||||
kind: src_kind,
|
||||
// if the filename is the same, we'll keep the source
|
||||
// original filename (if it exists; otherwise we overwrite
|
||||
// it)
|
||||
original_name: if src == tgt { src_original } else { None },
|
||||
inum: src_inum,
|
||||
},
|
||||
),
|
||||
|
55
tests/rename_fancy_restore.sh
Executable file
55
tests/rename_fancy_restore.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$OUT" "$EXP"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
OUT=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
|
||||
printf '{"he":{"dot":"shlishi"},"imnewhere":"derp","it":{".":"primo","..":"secondo"}}' >"$EXP"
|
||||
|
||||
ffs -m "$MNT" -o "$OUT" --target json ../json/obj_rename.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
case $(ls "$MNT") in
|
||||
(dot*dot_*dotdot*dotdot_) ;;
|
||||
(*) fail ls;;
|
||||
esac
|
||||
[ "$(cat $MNT/dot)" = "first" ] || fail dot
|
||||
[ "$(cat $MNT/dotdot)" = "second" ] || fail dotdot
|
||||
[ "$(cat $MNT/dot_)" = "third" ] || fail dot_
|
||||
[ "$(cat $MNT/dotdot_)" = "fourth" ] || fail dotdot_
|
||||
|
||||
echo primo >"$MNT"/dot
|
||||
echo secondo >"$MNT"/dotdot
|
||||
echo shlishi >"$MNT"/dot_
|
||||
echo derp >"$MNT"/dotdot_
|
||||
|
||||
mkdir "$MNT"/it
|
||||
mkdir "$MNT"/he
|
||||
|
||||
mv "$MNT"/dot "$MNT"/it
|
||||
mv "$MNT"/dotdot "$MNT"/it
|
||||
|
||||
mv "$MNT"/dot_ "$MNT"/he
|
||||
|
||||
mv "$MNT"/dotdot_ "$MNT"/imnewhere
|
||||
|
||||
umount "$MNT" || fail unmount
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process
|
||||
|
||||
diff "$OUT" "$EXP" || fail diff
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$OUT" "$EXP"
|
45
tests/rename_restore.sh
Executable file
45
tests/rename_restore.sh
Executable file
@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
fail() {
|
||||
echo FAILED: $1
|
||||
if [ "$MNT" ]
|
||||
then
|
||||
cd
|
||||
umount "$MNT"
|
||||
rmdir "$MNT"
|
||||
rm "$OUT" "$EXP"
|
||||
fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
MNT=$(mktemp -d)
|
||||
OUT=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
|
||||
printf '{".":"primo","..":"secondo","dot":"terzo","dotdot":"quarto"}' >"$EXP"
|
||||
|
||||
ffs -m "$MNT" -o "$OUT" --target json ../json/obj_rename.json &
|
||||
PID=$!
|
||||
sleep 2
|
||||
case $(ls "$MNT") in
|
||||
(dot*dot_*dotdot*dotdot_) ;;
|
||||
(*) fail ls;;
|
||||
esac
|
||||
[ "$(cat $MNT/dot)" = "first" ] || fail dot
|
||||
[ "$(cat $MNT/dotdot)" = "second" ] || fail dotdot
|
||||
[ "$(cat $MNT/dot_)" = "third" ] || fail dot_
|
||||
[ "$(cat $MNT/dotdot_)" = "fourth" ] || fail dotdot_
|
||||
|
||||
echo primo >"$MNT"/dot
|
||||
echo secondo >"$MNT"/dotdot
|
||||
echo terzo >"$MNT"/dot_
|
||||
echo quarto >"$MNT"/dotdot_
|
||||
|
||||
umount "$MNT" || fail unmount
|
||||
sleep 1
|
||||
kill -0 $PID >/dev/null 2>&1 && fail process
|
||||
|
||||
diff "$OUT" "$EXP" || fail diff
|
||||
|
||||
rmdir "$MNT" || fail mount
|
||||
rm "$OUT" "$EXP"
|
Loading…
Reference in New Issue
Block a user