aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-12 02:29:46 +0000
committertv <tv@krebsco.de>2026-11-12 03:36:09 +0100
commit96b4ae2984e7b0b1d9970acbb20b9165b1e3f2f0 (patch)
treefefcced4b5eab51d7ba2782a5fb8ca7eb2a2fd75 /src/document
parent62fd76ff79404ff1c474248f6ce4c13494b52866 (diff)
document file: format version 3HEADmaster
The document file's first line names its format; each record's first byte its kind. A version that doesn't know a kind refuses the file, as it does a format it doesn't know. With the Closed and Opened records, the format is continuum-document/3, so a version knowing only 2 refuses a file it can't read by its first line. Files of 2 are read as of 3: the same records, Closed and Opened where a version knowing them wrote them.
Diffstat (limited to 'src/document')
-rw-r--r--src/document/file.rs20
1 files changed, 15 insertions, 5 deletions
diff --git a/src/document/file.rs b/src/document/file.rs
index 54ab9af..a21aa17 100644
--- a/src/document/file.rs
+++ b/src/document/file.rs
@@ -1,7 +1,7 @@
//! The document file (`--document-file`): this node's document on disk, so a restart
//! resumes with it instead of starting empty.
//!
-//! Format: the line `continuum-document/2`, a header line (JSON: the actor IDs this
+//! Format: the line `continuum-document/3`, a header line (JSON: the actor IDs this
//! node wrote with, so a later rejoin replays those writes as its own, and the one it
//! writes with, `current`), then records, each a little-endian `u32` length and that
//! many bytes, the first of them its kind (`Record`): first a full save of the document,
@@ -11,8 +11,10 @@
//! holds everything the node wrote, so it may resume as `current` (`--keep-actor`); and
//! as a node starts on the file, before it writes anything, an empty `Opened` record,
//! which undoes that mark. A record cut short by a crash is dropped when reading; one of
-//! a kind this version doesn't know (a later version's) makes the file unusable. A file of `continuum-document/1` has records of the document alone,
-//! without their kind: a full save, then increments.
+//! a kind this version doesn't know (a later version's) makes the file unusable. A file
+//! of `continuum-document/2` is read as one of 3 (it has the same records, `Closed` and
+//! `Opened` where a version knowing them wrote it); one of `continuum-document/1` has
+//! records of the document alone, without their kind: a full save, then increments.
//!
//! Written by a worker (`swarm::document_file`), only while the node is ready and not
//! rejoining (a rejoin keeps the old file until it is done):
@@ -37,7 +39,9 @@ use crate::document::replica::Epoch;
use crate::files::{FileKind, Files};
use crate::locks::MutexExt;
-const MAGIC: &[u8] = b"continuum-document/2\n";
+const MAGIC: &[u8] = b"continuum-document/3\n";
+/// The format before `Closed` and `Opened`, still read as `MAGIC`.
+const MAGIC_2: &[u8] = b"continuum-document/2\n";
/// The format before signatures, still read.
const MAGIC_1: &[u8] = b"continuum-document/1\n";
@@ -100,7 +104,10 @@ pub(crate) fn load(path: &Path, files: &Files) -> Result<Option<Loaded>> {
/// The file's content, and how many bytes at its end were cut short.
fn parse(bytes: &[u8]) -> Result<(Loaded, usize)> {
- let (rest, kinds) = match bytes.strip_prefix(MAGIC) {
+ let (rest, kinds) = match bytes
+ .strip_prefix(MAGIC)
+ .or_else(|| bytes.strip_prefix(MAGIC_2))
+ {
Some(rest) => (rest, true),
None => (
bytes
@@ -537,6 +544,9 @@ mod tests {
assert!(loaded.doc.get(ROOT, "b").unwrap().is_some());
assert!(parse(b"something else").is_err());
+ let mut two = MAGIC_2.to_vec();
+ two.extend_from_slice(&bytes[MAGIC.len()..]);
+ assert!(parse(&two).unwrap().0.doc.get(ROOT, "b").unwrap().is_some());
assert!(parse(&bytes[..MAGIC.len() + 3]).is_err());
}