1// SPDX-License-Identifier: MIT
2
3package vm
4
5import (
6 "encoding/json"
7 "errors"
8 "os"
9 "path/filepath"
10 "strings"
11)
12
13// record is the on-disk trace of one VM. It is written before the first
14// resource is created and rewritten as resources appear, so that Reap can
15// remove them after a crash of the runner. The record is deleted last.
16type record struct {
17 ID string `json:"id"`
18 Slot int `json:"slot"`
19 Job string `json:"job,omitempty"`
20 PID int `json:"pid,omitempty"`
21 Tap string `json:"tap,omitempty"`
22 Gate string `json:"gateway,omitempty"`
23 Dir string `json:"dir,omitempty"` // the jail directory of this VM (removed whole)
24 Files []string `json:"files,omitempty"`
25}
26
27type store struct{ dir string }
28
29func (s store) path(id string) string { return filepath.Join(s.dir, id+".json") }
30
31func (s store) save(r record) error {
32 if err := os.MkdirAll(s.dir, 0o700); err != nil {
33 return err
34 }
35 b, err := json.Marshal(r)
36 if err != nil {
37 return err
38 }
39 tmp := s.path(r.ID) + ".tmp"
40 if err := os.WriteFile(tmp, b, 0o600); err != nil {
41 return err
42 }
43 return os.Rename(tmp, s.path(r.ID))
44}
45
46func (s store) remove(id string) error {
47 if err := os.Remove(s.path(id)); err != nil && !errors.Is(err, os.ErrNotExist) {
48 return err
49 }
50 return nil
51}
52
53// list returns every readable record; unreadable or corrupt files are returned
54// as ID-only records so that Reap still deletes them.
55func (s store) list() ([]record, error) {
56 ents, err := os.ReadDir(s.dir)
57 if errors.Is(err, os.ErrNotExist) {
58 return nil, nil
59 }
60 if err != nil {
61 return nil, err
62 }
63 var out []record
64 for _, e := range ents {
65 name := e.Name()
66 if e.IsDir() || !strings.HasSuffix(name, ".json") {
67 // A *.tmp left by a crash mid-write.
68 if strings.HasSuffix(name, ".tmp") {
69 _ = os.Remove(filepath.Join(s.dir, name))
70 }
71 continue
72 }
73 id := strings.TrimSuffix(name, ".json")
74 var r record
75 if b, err := os.ReadFile(filepath.Join(s.dir, name)); err == nil && json.Unmarshal(b, &r) == nil && r.ID == id {
76 out = append(out, r)
77 continue
78 }
79 out = append(out, record{ID: id, Slot: -1})
80 }
81 return out, nil
82}