-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstate.go
More file actions
118 lines (108 loc) · 3.35 KB
/
Copy pathstate.go
File metadata and controls
118 lines (108 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package gatekit
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
)
// snoozePath, disabledPath, and bypassLogPath are gatekit's own state
// files, all under Config.StateDir. This is the only directory gatekit ever
// writes to -- SourcesDir is module-owned and strictly read-only from here.
func snoozePath(cfg Config) string { return filepath.Join(cfg.StateDir, "snooze.json") }
func disabledPath(cfg Config) string { return filepath.Join(cfg.StateDir, "disabled") }
func bypassLogPath(cfg Config) string { return filepath.Join(cfg.StateDir, "bypass.log") }
// writeJSONAtomic writes v as JSON to path via a temp file in the same
// directory, then renames it into place. A reader (including gatekit's own
// next Evaluate call) can never observe a half-written file.
func writeJSONAtomic(path string, v any) error {
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
}
b = append(b, '\n')
f, err := os.CreateTemp(filepath.Dir(path), ".tmp-*")
if err != nil {
return err
}
tmp := f.Name()
defer os.Remove(tmp) // no-op once the rename below succeeds
if _, err := f.Write(b); err != nil {
f.Close()
return err
}
if err := f.Close(); err != nil {
return err
}
return os.Rename(tmp, path)
}
// readJSON decodes path into v. A missing file is reported via the returned
// error being os.ErrNotExist-wrapped, left for the caller to check with
// os.IsNotExist -- callers here always treat "absent" and "corrupt" as
// distinct outcomes on purpose (see snooze.go, killswitch.go).
func readJSON(path string, v any) error {
b, err := os.ReadFile(path)
if err != nil {
return err
}
return json.Unmarshal(b, v)
}
// appendJSONL appends one JSON-encoded line to path, creating it and its
// parent directory if needed. Best effort in spirit: callers decide whether
// a failure here should itself become a block (it should not -- an
// observability nail must never become a single point of failure).
func appendJSONL(path string, v any) error {
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
return err
}
defer f.Close()
b, err := json.Marshal(v)
if err != nil {
return err
}
_, err = f.Write(append(b, '\n'))
return err
}
// readJSONLReverse decodes path's lines newest-last-first (i.e. reverse file
// order), calling keep(v) for each and stopping the first time keep returns
// false. Malformed lines are skipped rather than aborting the scan. This is
// the shared shape both RequestBypass's rate-limit check and any future
// log-scanning code need: read backwards, stop as soon as you're out of the
// window you care about.
func readJSONLReverse[T any](path string, keep func(T) bool) ([]T, error) {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
defer f.Close()
var lines []string
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for sc.Scan() {
lines = append(lines, sc.Text())
}
if err := sc.Err(); err != nil {
return nil, err
}
var out []T
for i := len(lines) - 1; i >= 0; i-- {
var v T
if err := json.Unmarshal([]byte(lines[i]), &v); err != nil {
continue
}
if !keep(v) {
break
}
out = append(out, v)
}
return out, nil
}