1 package client
2
3 import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io/fs"
8 "os"
9 "path/filepath"
10 "sync"
11
12 "github.com/theupdateframework/go-tuf/client"
13 "github.com/theupdateframework/go-tuf/internal/fsutil"
14 "github.com/theupdateframework/go-tuf/util"
15 )
16
17 const (
18
19
20
21 dirCreateMode = os.FileMode(0750)
22
23
24
25 fileCreateMode = os.FileMode(0640)
26 )
27
28
29
30 type FileJSONStore struct {
31 mtx sync.RWMutex
32 baseDir string
33 }
34
35 var _ client.LocalStore = (*FileJSONStore)(nil)
36
37
38
39
40
41 func NewFileJSONStore(baseDir string) (*FileJSONStore, error) {
42 f := &FileJSONStore{
43 baseDir: baseDir,
44 }
45
46
47 fi, err := os.Stat(baseDir)
48 if err != nil {
49 if errors.Is(err, fs.ErrNotExist) {
50
51 if err = os.MkdirAll(baseDir, dirCreateMode); err != nil {
52 return nil, fmt.Errorf("error creating directory for metadata cache: %w", err)
53 }
54 } else {
55 return nil, fmt.Errorf("error getting FileInfo for %s: %w", baseDir, err)
56 }
57 } else {
58
59 if !fi.IsDir() {
60 return nil, fmt.Errorf("can not open %s, not a directory", baseDir)
61 }
62
63 if err = fsutil.EnsureMaxPermissions(fi, dirCreateMode); err != nil {
64 return nil, err
65 }
66 }
67
68 return f, nil
69 }
70
71
72 func (f *FileJSONStore) GetMeta() (map[string]json.RawMessage, error) {
73 f.mtx.RLock()
74 defer f.mtx.RUnlock()
75
76 names, err := os.ReadDir(f.baseDir)
77 if err != nil {
78 return nil, fmt.Errorf("error reading directory %s: %w", f.baseDir, err)
79 }
80
81 meta := map[string]json.RawMessage{}
82 for _, name := range names {
83 ok, err := fsutil.IsMetaFile(name)
84 if err != nil {
85 return nil, err
86 }
87 if !ok {
88 continue
89 }
90
91
92 info, err := name.Info()
93 if err != nil {
94 return nil, fmt.Errorf("error retrieving FileInfo for %s: %w", name.Name(), err)
95 }
96 if err = fsutil.EnsureMaxPermissions(info, fileCreateMode); err != nil {
97 return nil, err
98 }
99
100 p := filepath.Join(f.baseDir, name.Name())
101 b, err := os.ReadFile(p)
102 if err != nil {
103 return nil, fmt.Errorf("error reading file %s: %w", name.Name(), err)
104 }
105 meta[name.Name()] = b
106 }
107
108 return meta, nil
109 }
110
111
112
113 func (f *FileJSONStore) SetMeta(name string, meta json.RawMessage) error {
114 f.mtx.Lock()
115 defer f.mtx.Unlock()
116
117 if filepath.Ext(name) != ".json" {
118 return fmt.Errorf("file %s is not a JSON file", name)
119 }
120
121 p := filepath.Join(f.baseDir, name)
122 err := util.AtomicallyWriteFile(p, meta, fileCreateMode)
123 return err
124 }
125
126
127
128 func (f *FileJSONStore) DeleteMeta(name string) error {
129 f.mtx.Lock()
130 defer f.mtx.Unlock()
131
132 if filepath.Ext(name) != ".json" {
133 return fmt.Errorf("file %s is not a JSON file", name)
134 }
135
136 p := filepath.Join(f.baseDir, name)
137 err := os.Remove(p)
138 if err == nil {
139 return nil
140 }
141
142 return fmt.Errorf("error deleting file %s: %w", name, err)
143 }
144
145
146 func (f *FileJSONStore) Close() error {
147 return nil
148 }
149
View as plain text