mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
Merge #1171: Never overwrite a file's log when it can't be read (@Silvertree2010)
This commit is contained in:
@@ -1110,7 +1110,24 @@ impl Store {
|
||||
let mut issues = Vec::new();
|
||||
let mut fresh_log = true;
|
||||
let mut kept = 0;
|
||||
match disk::read_capped(&dir.join(LOG), self.cfg.max_file_bytes).and_then(|b| disk::read_log(&b)) {
|
||||
let log_path = dir.join(LOG);
|
||||
// A log that's there but can't be read (an IO error, or over a lowered cap) may hold
|
||||
// confirmed edits: the file isn't opened, and the log is left as it is for another try.
|
||||
let read = match disk::read_capped(&log_path, self.cfg.max_file_bytes) {
|
||||
Err(why) if log_path.exists() => return Err(format!("its log can't be read ({why}); it was left as it is")),
|
||||
r => r,
|
||||
};
|
||||
// One that isn't a log is moved aside before a new one replaces it (or the file isn't opened).
|
||||
let read = match read.map(|b| disk::read_log(&b)) {
|
||||
Ok(Err(why)) => {
|
||||
let aside = dir.join(format!("{LOG}.unreadable-{generation}-{}", now()));
|
||||
fs::rename(&log_path, &aside).map_err(|e| format!("{why}, and it couldn't be moved aside ({e})"))?;
|
||||
Err(format!("{why}; it was moved aside to {}", aside.display()))
|
||||
}
|
||||
Ok(Ok(log)) => Ok(log),
|
||||
Err(why) => Err(why),
|
||||
};
|
||||
match read {
|
||||
Ok(log) if log.generation == generation => {
|
||||
fresh_log = false;
|
||||
for entry in log.entries {
|
||||
|
||||
@@ -313,3 +313,54 @@ fn edits_confirmed_after_a_compaction_cut_short_survive_a_restart() {
|
||||
let name = page_name(&restored);
|
||||
assert!(at(&name) >= at(&confirmed), "restored {name:?}, confirmed {confirmed:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_log_that_cant_be_read_is_never_overwritten() {
|
||||
let dir = TempDir::new("unreadlog");
|
||||
let c = StoreConfig { compact_after: 1000, ..cfg(dir.path()) };
|
||||
let log_path = dir.path().join(FILES);
|
||||
let last = "Confirmed page name number 39 with some padding";
|
||||
let id = {
|
||||
let mut store = Store::open(dir.path(), c.clone()).unwrap();
|
||||
let (id, _, _) = publish(&mut store, "Busy");
|
||||
let mut server = store.open(&id).unwrap();
|
||||
let mut a = join(&mut server, Access::Edit);
|
||||
store.changed(&id, &mut server);
|
||||
for i in 0..40 {
|
||||
rename(&mut server, &mut a, &format!("Confirmed page name number {i} with some padding"));
|
||||
store.changed(&id, &mut server);
|
||||
}
|
||||
assert!(store.flush().is_empty());
|
||||
id
|
||||
};
|
||||
let log_path = log_path.join(&id).join(LOG);
|
||||
let before = fs::read(&log_path).unwrap();
|
||||
// The cap lowered just under the log: it can't be read now, but it must still be there after.
|
||||
let lowered = StoreConfig { max_file_bytes: before.len() as u64 - 1, ..c.clone() };
|
||||
{
|
||||
let mut store = Store::open(dir.path(), lowered).unwrap();
|
||||
let opened = store.open(&id);
|
||||
assert!(opened.is_err(), "the file isn't opened without its log");
|
||||
assert!(!store.problems().is_empty(), "the problem is reported");
|
||||
assert_eq!(fs::read(&log_path).unwrap(), before, "the log is left as it was");
|
||||
}
|
||||
let mut store = Store::open(dir.path(), c).unwrap();
|
||||
let server = store.open(&id).unwrap();
|
||||
assert_eq!(page_name(&server), last, "every confirmed edit comes back");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_log_that_isnt_one_is_moved_aside() {
|
||||
let dir = TempDir::new("badlog");
|
||||
let mut store = Store::open(dir.path(), cfg(dir.path())).unwrap();
|
||||
let (id, _, _) = publish(&mut store, "Busy");
|
||||
let folder = dir.path().join(FILES).join(&id);
|
||||
let junk = b"not a log at all, but maybe worth keeping".to_vec();
|
||||
fs::write(folder.join(LOG), &junk).unwrap();
|
||||
drop(store);
|
||||
let mut store = Store::open(dir.path(), cfg(dir.path())).unwrap();
|
||||
store.open(&id).unwrap();
|
||||
assert!(store.problems().iter().any(|p| p.contains("moved aside")), "{:?}", store.problems());
|
||||
let kept = fs::read_dir(&folder).unwrap().filter_map(|e| e.ok()).any(|e| fs::read(e.path()).is_ok_and(|b| b == junk));
|
||||
assert!(kept, "the old log's bytes are kept beside the file");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user