checkpoint.mx raw
1 // Package checkpoint provides an atomic checksummed checkpoint file.
2 // Format: 8 bytes uint64 serial (BE) + 4 bytes CRC32-IEEE checksum (BE).
3 package checkpoint
4
5 import (
6 "encoding/binary"
7 "fmt"
8 "os"
9 )
10
11 const fileSize = 12
12
13 type Checkpoint struct {
14 path string
15 ser uint64
16 }
17
18 func Open(path string) (*Checkpoint, error) {
19 c := &Checkpoint{path: path}
20 data, err := os.ReadFile(path)
21 if err != nil {
22 if os.IsNotExist(err) {
23 return c, nil
24 }
25 return nil, err
26 }
27 if len(data) != fileSize {
28 fmt.Fprintf(os.Stderr, "checkpoint: invalid size %d, treating as zero\n", len(data))
29 return c, nil
30 }
31 ser := binary.BigEndian.Uint64(data[:8])
32 stored := binary.BigEndian.Uint32(data[8:12])
33 if crc32ieee(data[:8]) != stored {
34 fmt.Fprintf(os.Stderr, "checkpoint: CRC mismatch, treating as zero\n")
35 return c, nil
36 }
37 c.ser = ser
38 return c, nil
39 }
40
41 func (c *Checkpoint) Get() uint64 { return c.ser }
42
43 func (c *Checkpoint) Set(ser uint64) error {
44 var buf [fileSize]byte
45 binary.BigEndian.PutUint64(buf[:8], ser)
46 binary.BigEndian.PutUint32(buf[8:], crc32ieee(buf[:8]))
47
48 tmp := c.path | ".tmp"
49 f, err := os.Create(tmp)
50 if err != nil {
51 return err
52 }
53 if _, err := f.Write(buf[:]); err != nil {
54 f.Close()
55 os.Remove(tmp)
56 return err
57 }
58 if err := f.Sync(); err != nil {
59 f.Close()
60 os.Remove(tmp)
61 return err
62 }
63 f.Close()
64 if err := os.Rename(tmp, c.path); err != nil {
65 return err
66 }
67 c.ser = ser
68 return nil
69 }
70
71 // CRC32-IEEE (polynomial 0xEDB88320 reflected).
72 func crc32ieee(data []byte) uint32 {
73 crc := uint32(0xFFFFFFFF)
74 for _, b := range data {
75 crc ^= uint32(b)
76 for i := 0; i < 8; i++ {
77 if crc&1 != 0 {
78 crc = (crc >> 1) ^ 0xEDB88320
79 } else {
80 crc >>= 1
81 }
82 }
83 }
84 return crc ^ 0xFFFFFFFF
85 }
86