diff --git a/pkg/storage/fs.go b/pkg/storage/fs.go new file mode 100644 index 0000000..1ad67b4 --- /dev/null +++ b/pkg/storage/fs.go @@ -0,0 +1,28 @@ +package storage + +import ( + "errors" + "sync" +) + +var ( + ErrNoSuchKey = errors.New("no such key in store") +) + +type Item interface { + sync.Locker + Dirty() + IsDirty() bool + GetData() interface{} + SetData(interface{}) + ItemKey() string +} + +type Store interface { + GetItem(key string, data interface{}) (Item, error) + Get(key string, data interface{}) error + Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error) + FlushAll() []error + Flush(key string) error + Shutdown() +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 1e4d0c4..c051dad 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -2,55 +2,43 @@ package storage import ( "encoding/json" - "errors" "fmt" "io/fs" "os" "path" "strings" + "sync" "github.com/rs/zerolog/log" ) var ( IndentStr = strings.Repeat(" ", 4) - - ErrNoSuchKey = errors.New("no such key in store") ) const ( - SecretMode os.FileMode = 0600 - DefaultMode os.FileMode = 0644 + SecretMode fs.FileMode = 0600 + DefaultMode fs.FileMode = 0644 ) -type Data interface { -} - type item struct { + sync.Mutex `json:"-"` Version int `json:"version"` MinorVersion *int `json:"minor_version,omitempty"` Key string `json:"key"` Data interface{} `json:"data"` - fmode os.FileMode + fmode fs.FileMode dirty bool } -type Item interface { - Dirty() - IsDirty() bool - GetData() interface{} - SetData(interface{}) - ItemKey() string -} +func (i *item) Dirty() { i.Lock(); defer i.Unlock(); i.dirty = true } +func (i *item) IsDirty() bool { i.Lock(); defer i.Unlock(); return i.dirty } +func (i *item) GetData() interface{} { i.Lock(); defer i.Unlock(); return i.Data } +func (i *item) SetData(d interface{}) { i.Lock(); defer i.Unlock(); i.Data = d; i.dirty = true } +func (i *item) ItemKey() string { return i.Key /* key is immutable */ } -func (i *item) Dirty() { i.dirty = true } -func (i *item) IsDirty() bool { return i.dirty } -func (i *item) GetData() interface{} { return i.Data } -func (i *item) SetData(d interface{}) { i.Data = d; i.Dirty() } -func (i *item) ItemKey() string { return i.Key } - -func (it *item) mode() os.FileMode { +func (it *item) mode() fs.FileMode { if it.fmode != 0 { return it.fmode } @@ -59,21 +47,35 @@ func (it *item) mode() os.FileMode { } type fsStore struct { - fs.FS + sync.RWMutex + fs fs.FS storeRoot string s map[string]*item } -type Store interface { - GetItem(key string, data interface{}) (Item, error) - Get(key string, data interface{}) error - Put(key string, version, minorVersion int, secretMode bool, data interface{}) (Item, error) - FlushAll() []error - Flush(key string) error - Shutdown() +func (s *fsStore) get(key string) *item { + s.RLock() + defer s.RUnlock() + + i, ok := s.s[key] + if !ok { + return nil + } + + return i +} + +func (s *fsStore) put(key string, it *item) { + s.Lock() + defer s.Unlock() + + s.s[key] = it } func (s *fsStore) persist(it *item) error { + it.Lock() + defer it.Unlock() + f, err := os.OpenFile(path.Join(s.storeRoot, it.Key), os.O_WRONLY|os.O_CREATE, it.mode()) if err != nil { return err @@ -93,19 +95,19 @@ func (s *fsStore) persist(it *item) error { } func (s *fsStore) Dirty(key string) error { - it, has := s.s[key] - if !has { + it := s.get(key) + if it == nil { return ErrNoSuchKey } - it.dirty = true + it.Dirty() return nil } func (s *fsStore) Flush(key string) error { - it, exists := s.s[key] - if !exists { + it := s.get(key) + if it == nil { return ErrNoSuchKey } @@ -113,6 +115,9 @@ func (s *fsStore) Flush(key string) error { } func (s *fsStore) FlushAll() []error { + s.RLock() + defer s.RUnlock() + var errs []error for _, it := range s.s { err := s.persist(it) @@ -167,15 +172,21 @@ func (s *fsStore) Get(key string, data interface{}) error { } func (s *fsStore) GetItem(key string, data interface{}) (Item, error) { - f, err := s.Open(key) + f, err := s.fs.Open(key) if err != nil { return nil, err } defer f.Close() + fi, err := f.Stat() + if err != nil { + return nil, err + } + item := &item{ - Data: data, + Data: data, + fmode: fi.Mode(), } d := json.NewDecoder(f) err = d.Decode(item) @@ -187,7 +198,7 @@ func (s *fsStore) GetItem(key string, data interface{}) (Item, error) { return nil, fmt.Errorf("key mismatch '%s' != '%s'", item.Key, key) } - s.s[key] = item + s.put(key, item) return item, nil } @@ -197,7 +208,7 @@ func OpenFileStore(configRoot string) (*fsStore, error) { stor := os.DirFS(storeRoot) return &fsStore{ - FS: stor, + fs: stor, storeRoot: storeRoot, s: make(map[string]*item), }, nil