Files
mesh-sdk/go/busarchive/verifyfile.go
T
jschoubben 5fa64be894 Revert the revert of G's mesh-sdk merge (#191): G's sdk stands again over reader release 3
Main reverted G's half merge (#189, reverted by 8afc3eb in #191) and then took the reader release's S1-S5, which
copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the
release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's
empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's head f30dee54 only by those and
the release's two tests (objects cap option, seat states and stream kinds).

Builds: design 47 G (the milestone's sdk part, as at f30dee54)

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-13 12:49:56 +01:00

110 lines
3.2 KiB
Go

package busarchive
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
)
// StreamReport is one stream of a verified snapshot.
type StreamReport struct {
Name string `json:"name"`
LastSeq uint64 `json:"last_seq"`
}
// Report is the answer of VerifyFile: which file was held to its manifest, and whether it passed.
type Report struct {
File string `json:"file"`
Modified time.Time `json:"modified"`
Size int64 `json:"size"`
Taken *time.Time `json:"taken,omitempty"`
Streams []StreamReport `json:"streams"`
Skipped []Skipped `json:"skipped,omitempty"`
Passed bool `json:"passed"`
// Fault is the first thing wrong, empty when the snapshot passed.
Fault string `json:"fault,omitempty"`
}
// VerifyFile verifies one snapshot inside dir: the file called name, or the newest *.tar when name is
// empty. Nothing outside dir is read and nothing is written there; a snapshot that fails is an answer
// (Passed false, the first Fault), and only a directory without a snapshot is an error.
func VerifyFile(dir, name string) (*Report, error) {
path, info, err := pick(dir, name)
if err != nil {
return nil, err
}
r := &Report{File: filepath.Base(path), Modified: info.ModTime().UTC(), Size: info.Size(), Streams: []StreamReport{}}
f, err := os.Open(path)
if err != nil {
r.Fault = err.Error()
return r, nil
}
defer f.Close()
a, err := ReadArchive(f, "")
if err != nil {
r.Fault = err.Error()
return r, nil
}
defer a.Close()
taken := a.Manifest.Finished
r.Taken = &taken
r.Skipped = a.Manifest.Skipped
for _, s := range a.Manifest.Streams {
r.Streams = append(r.Streams, StreamReport{Name: s.Name, LastSeq: s.LastSeq})
}
if err := a.Verify(); err != nil {
r.Fault = err.Error()
return r, nil
}
r.Passed = true
return r, nil
}
// pick names the regular file to verify, and nothing outside dir: a name is one path component, and a
// symbolic link is not followed out of the directory.
func pick(dir, name string) (string, fs.FileInfo, error) {
if dir == "" {
return "", nil, errors.New("the snapshots directory is not set")
}
if name != "" {
if name != filepath.Base(name) || name == "." || name == ".." || strings.ContainsRune(name, 0) {
return "", nil, fmt.Errorf("%q is not a file name inside the snapshots directory", name)
}
p := filepath.Join(dir, name)
info, err := os.Lstat(p)
if err != nil {
return "", nil, fmt.Errorf("no snapshot %s in the snapshots directory: %w", name, err)
}
if !info.Mode().IsRegular() {
return "", nil, fmt.Errorf("%s is not a regular file", name)
}
return p, info, nil
}
entries, err := os.ReadDir(dir)
if err != nil {
return "", nil, fmt.Errorf("the snapshots directory does not read: %w", err)
}
var best string
var bestInfo fs.FileInfo
for _, e := range entries {
if !strings.HasSuffix(e.Name(), ".tar") {
continue
}
info, err := e.Info()
if err != nil || !info.Mode().IsRegular() {
continue
}
if bestInfo == nil || info.ModTime().After(bestInfo.ModTime()) {
best, bestInfo = e.Name(), info
}
}
if bestInfo == nil {
return "", nil, errors.New("the snapshots directory holds no .tar snapshot")
}
return filepath.Join(dir, best), bestInfo, nil
}