oficina / home Public

Recover floor markers from immutable signed history

Paul Campbell pushed by paul@keymail.dev 0033b6da4192a59a54dfc7bede066fe6d6b36c41 parent e0db957
2 files changed, +69 −5
  • internal/provisioner/floor_linux.go +31 −5
  • internal/provisioner/floor_linux_test.go +38 −0
diff --git a/internal/provisioner/floor_linux.go b/internal/provisioner/floor_linux.go
index e4a39b9..bc3667c 100644
--- a/internal/provisioner/floor_linux.go
+++ b/internal/provisioner/floor_linux.go
@@ -15,6 +15,7 @@ import (
family "amadan.net/rastrillo/rastrillo/crypto"
"github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
+ "golang.org/x/sys/unix"
)
const floorContext = "oficina/provisioner/floor/v1"
@@ -109,7 +110,10 @@ func (f *nativeFloor) initialize(k *family.Keypair) error {
if e != nil {
return e
}
- return f.dir.create(floorName(0), raw)
+ if e = f.dir.create(floorName(0), raw); e != nil {
+ return e
+ }
+ return f.updateMarker(floorPoint{})
}
func (f *nativeFloor) names() ([]string, error) {
// Open a fresh directory description: a retained Readdir offset must never
@@ -127,16 +131,16 @@ func (f *nativeFloor) names() ([]string, error) {
if e != nil || !os.SameFile(oldInfo, newInfo) {
return nil, ErrRefused
}
- entries, e := fd.file.Readdirnames(maxRecords + 3)
+ entries, e := fd.file.Readdirnames(maxRecords + 5)
if e != nil && e != io.EOF {
return nil, ErrUnavailable
}
- if len(entries) > maxRecords+2 {
+ if len(entries) > maxRecords+4 {
return nil, ErrRefused
}
names := make([]string, 0, len(entries))
for _, name := range entries {
- if name == "lock" {
+ if name == "lock" || name == "head" || name == "head.tmp" {
continue
}
if len(name) != 25 || name[20:] != ".json" {
@@ -207,5 +211,27 @@ func (f *nativeFloor) advance(k *family.Keypair, expected, next floorPoint) erro
if e != nil {
return e
}
- return f.dir.create(floorName(next.Sequence), raw)
+ if e = f.dir.create(floorName(next.Sequence), raw); e != nil {
+ return e
+ }
+ return f.updateMarker(next)
+}
+
+// The marker is an optimization only. Every open scans signed immutable records,
+// including a durable newer record written before a marker-update crash.
+func (f *nativeFloor) updateMarker(point floorPoint) error {
+ raw, e := canonicalJSON(point)
+ if e != nil {
+ return e
+ }
+ if e = f.dir.create("head.tmp", raw); e != nil {
+ existing, readErr := f.dir.read("head.tmp", 8192)
+ if readErr != nil || !bytes.Equal(existing, raw) {
+ return ErrRefused
+ }
+ }
+ if unix.Renameat(int(f.dir.file.Fd()), "head.tmp", int(f.dir.file.Fd()), "head") != nil || f.dir.file.Sync() != nil {
+ return ErrUnavailable
+ }
+ return nil
}
diff --git a/internal/provisioner/floor_linux_test.go b/internal/provisioner/floor_linux_test.go
index 9ad72a0..60134fa 100644
--- a/internal/provisioner/floor_linux_test.go
+++ b/internal/provisioner/floor_linux_test.go
@@ -111,3 +111,41 @@ func TestFloorRefusesPartialTamperedAndMissingHistory(t *testing.T) {
})
}
}
+
+func TestFloorScanSeesDurableRecordBeforeMarkerUpdate(t *testing.T) {
+ f, k := floorFixture(t)
+ if e := f.initialize(k); e != nil {
+ t.Fatal(e)
+ }
+ old, previous, e := f.current()
+ if e != nil {
+ t.Fatal(e)
+ }
+ next := floorPoint{1, testToken(8)}
+ raw, e := f.signed(k, next, previous)
+ if e != nil {
+ t.Fatal(e)
+ }
+ if e = f.dir.create(floorName(1), raw); e != nil {
+ t.Fatal(e)
+ }
+ // The marker remains genesis, as after power loss between durable record and
+ // marker replacement. It cannot authorize reusing the previous sequence.
+ current, _, e := f.current()
+ if e != nil || current != next {
+ t.Fatal("old marker hid durable record", e)
+ }
+ if e = f.advance(k, old, next); e != ErrRefused {
+ t.Fatal("old marker allowed replay", e)
+ }
+ marker, e := canonicalJSON(next)
+ if e != nil {
+ t.Fatal(e)
+ }
+ if e = f.dir.create("head.tmp", marker); e != nil {
+ t.Fatal(e)
+ }
+ if e = f.updateMarker(next); e != nil {
+ t.Fatal("matching interrupted marker did not resume", e)
+ }
+}