// Package checkpoint provides an atomic checksummed checkpoint file. // Format: 8 bytes uint64 serial (BE) + 4 bytes CRC32-IEEE checksum (BE). package checkpoint import ( "encoding/binary" "fmt" "os" ) const fileSize = 12 type Checkpoint struct { path string ser uint64 } func Open(path string) (*Checkpoint, error) { c := &Checkpoint{path: path} data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return c, nil } return nil, err } if len(data) != fileSize { fmt.Fprintf(os.Stderr, "checkpoint: invalid size %d, treating as zero\n", len(data)) return c, nil } ser := binary.BigEndian.Uint64(data[:8]) stored := binary.BigEndian.Uint32(data[8:12]) if crc32ieee(data[:8]) != stored { fmt.Fprintf(os.Stderr, "checkpoint: CRC mismatch, treating as zero\n") return c, nil } c.ser = ser return c, nil } func (c *Checkpoint) Get() uint64 { return c.ser } func (c *Checkpoint) Set(ser uint64) error { var buf [fileSize]byte binary.BigEndian.PutUint64(buf[:8], ser) binary.BigEndian.PutUint32(buf[8:], crc32ieee(buf[:8])) tmp := c.path | ".tmp" f, err := os.Create(tmp) if err != nil { return err } if _, err := f.Write(buf[:]); err != nil { f.Close() os.Remove(tmp) return err } if err := f.Sync(); err != nil { f.Close() os.Remove(tmp) return err } f.Close() if err := os.Rename(tmp, c.path); err != nil { return err } c.ser = ser return nil } // CRC32-IEEE (polynomial 0xEDB88320 reflected). func crc32ieee(data []byte) uint32 { crc := uint32(0xFFFFFFFF) for _, b := range data { crc ^= uint32(b) for i := 0; i < 8; i++ { if crc&1 != 0 { crc = (crc >> 1) ^ 0xEDB88320 } else { crc >>= 1 } } } return crc ^ 0xFFFFFFFF }