aboutsummaryrefslogtreecommitdiff
path: root/internal/persistence/txn.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/persistence/txn.go')
-rw-r--r--internal/persistence/txn.go20
1 files changed, 20 insertions, 0 deletions
diff --git a/internal/persistence/txn.go b/internal/persistence/txn.go
index 400d355..1049077 100644
--- a/internal/persistence/txn.go
+++ b/internal/persistence/txn.go
@@ -179,6 +179,9 @@ func (t *Transaction) Replace(path string, data []byte, perm os.FileMode) error
if err != nil {
return err
}
+ if op.Kind == kindMove {
+ return fmt.Errorf("conflicting operation on %s: already moved to %s", path, op.MoveTo)
+ }
if op.Kind == kindMoveTarget {
op.New = data
op.NewExists = true
@@ -198,6 +201,9 @@ func (t *Transaction) Delete(path string) error {
if err != nil {
return err
}
+ if op.Kind == kindMove {
+ return fmt.Errorf("conflicting operation on %s: already moved to %s", path, op.MoveTo)
+ }
op.Kind = kindDelete
op.New, op.NewExists = nil, false
return nil
@@ -217,6 +223,9 @@ func (t *Transaction) Move(oldPath, newPath string) error {
if src.Kind == kindMove && src.MoveTo != newPath {
return fmt.Errorf("conflicting move of %s", oldPath)
}
+ if src.Kind == kindReplace || src.Kind == kindDelete {
+ return fmt.Errorf("conflicting operation on %s: already replaced or deleted", oldPath)
+ }
if dst.Kind != kindMoveTarget && dst.Kind != "" {
return fmt.Errorf("conflicting operation on %s", newPath)
}
@@ -370,6 +379,12 @@ func (t *Transaction) rollback() error {
if err := restore(op.Path); err != nil {
record(fmt.Errorf("restore %s: %w", op.Path, err))
}
+ default:
+ // An unknown kind (a journal written by a newer version, or a
+ // hand-edited file) must not be silently skipped: the before-image
+ // would be dropped and the journal then deleted, losing the only
+ // record of the pre-transaction state. Fail so Recover keeps it.
+ record(fmt.Errorf("unknown op kind %q for %s", op.Kind, op.Path))
}
}
return firstErr
@@ -381,6 +396,11 @@ func Recover(journalDir string) error {
if err := os.MkdirAll(journalDir, 0700); err != nil {
return err
}
+ // Same durability requirement as Commit: persist the directory entry so a
+ // crash cannot erase the journals before they are recovered.
+ if err := syncDir(filepath.Dir(journalDir)); err != nil {
+ return fmt.Errorf("sync journal parent: %w", err)
+ }
entries, err := os.ReadDir(journalDir)
if err != nil {
return err