1
1
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:
Michael Greenberg 2021-07-02 08:01:07 -07:00 committed by GitHub
parent 7357d88353
commit 726a175163
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 182 additions and 16 deletions

View File

@ -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)
}

View File

@ -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)

View File

@ -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
View 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
View 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"