Update codeberg.org/gruf libraries and fix go-store issue (#347)

* update codeberg.org/gruf/ libraries

Signed-off-by: kim <grufwub@gmail.com>

* another update

Signed-off-by: kim <grufwub@gmail.com>
This commit is contained in:
kim 2021-12-20 09:35:32 +00:00 committed by GitHub
commit 635ad2a42f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 366 additions and 197 deletions

View file

@ -2,6 +2,7 @@ package kv
import (
"io"
"sync"
"codeberg.org/gruf/go-errors"
)
@ -15,9 +16,14 @@ var ErrStateClosed = errors.New("store/kv: state closed")
// then the state has zero guarantees
type StateRO struct {
store *KVStore
mutex sync.RWMutex
}
func (st *StateRO) Get(key string) ([]byte, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return nil, ErrStateClosed
@ -28,6 +34,10 @@ func (st *StateRO) Get(key string) ([]byte, error) {
}
func (st *StateRO) GetStream(key string) (io.ReadCloser, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return nil, ErrStateClosed
@ -38,6 +48,10 @@ func (st *StateRO) GetStream(key string) (io.ReadCloser, error) {
}
func (st *StateRO) Has(key string) (bool, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return false, ErrStateClosed
@ -47,8 +61,16 @@ func (st *StateRO) Has(key string) (bool, error) {
return st.store.has(key)
}
func (st *StateRO) close() {
st.store = nil
func (st *StateRO) Release() {
// Get state write lock
st.mutex.Lock()
defer st.mutex.Unlock()
// Release the store
if st.store != nil {
st.store.mutex.RUnlock()
st.store = nil
}
}
// StateRW provides a read-write window to the store. While this
@ -58,9 +80,14 @@ func (st *StateRO) close() {
// then the state has zero guarantees
type StateRW struct {
store *KVStore
mutex sync.RWMutex
}
func (st *StateRW) Get(key string) ([]byte, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return nil, ErrStateClosed
@ -71,6 +98,10 @@ func (st *StateRW) Get(key string) ([]byte, error) {
}
func (st *StateRW) GetStream(key string) (io.ReadCloser, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return nil, ErrStateClosed
@ -81,6 +112,10 @@ func (st *StateRW) GetStream(key string) (io.ReadCloser, error) {
}
func (st *StateRW) Put(key string, value []byte) error {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return ErrStateClosed
@ -91,6 +126,10 @@ func (st *StateRW) Put(key string, value []byte) error {
}
func (st *StateRW) PutStream(key string, r io.Reader) error {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return ErrStateClosed
@ -101,6 +140,10 @@ func (st *StateRW) PutStream(key string, r io.Reader) error {
}
func (st *StateRW) Has(key string) (bool, error) {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return false, ErrStateClosed
@ -111,6 +154,10 @@ func (st *StateRW) Has(key string) (bool, error) {
}
func (st *StateRW) Delete(key string) error {
// Get state read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Check not closed
if st.store == nil {
return ErrStateClosed
@ -120,6 +167,14 @@ func (st *StateRW) Delete(key string) error {
return st.store.delete(key)
}
func (st *StateRW) close() {
st.store = nil
func (st *StateRW) Release() {
// Get state write lock
st.mutex.Lock()
defer st.mutex.Unlock()
// Release the store
if st.store != nil {
st.store.mutex.Unlock()
st.store = nil
}
}

View file

@ -212,32 +212,34 @@ func (st *KVStore) Iterator(matchFn func(string) bool) (*KVIterator, error) {
}, nil
}
// Read provides a read-only window to the store, holding it in a read-locked state until
// the supplied function returns
func (st *KVStore) Read(do func(*StateRO)) {
// Get store read lock
// Read provides a read-only window to the store, holding it in a read-locked state until release
func (st *KVStore) Read() *StateRO {
st.mutex.RLock()
defer st.mutex.RUnlock()
// Create new store state (defer close)
state := &StateRO{store: st}
defer state.close()
// Pass state
do(state)
return &StateRO{store: st}
}
// Update provides a read-write window to the store, holding it in a read-write-locked state
// until the supplied functions returns
func (st *KVStore) Update(do func(*StateRW)) {
// Get store lock
// ReadFn provides a read-only window to the store, holding it in a read-locked state until fn return.
func (st *KVStore) ReadFn(fn func(*StateRO)) {
// Acquire read-only state
state := st.Read()
defer state.Release()
// Pass to fn
fn(state)
}
// Update provides a read-write window to the store, holding it in a write-locked state until release
func (st *KVStore) Update() *StateRW {
st.mutex.Lock()
defer st.mutex.Unlock()
// Create new store state (defer close)
state := &StateRW{store: st}
defer state.close()
// Pass state
do(state)
return &StateRW{store: st}
}
// UpdateFn provides a read-write window to the store, holding it in a write-locked state until fn return.
func (st *KVStore) UpdateFn(fn func(*StateRW)) {
// Acquire read-write state
state := st.Update()
defer state.Release()
// Pass to fn
fn(state)
}

View file

@ -585,8 +585,8 @@ func (st *BlockStorage) WalkKeys(opts WalkKeysOptions) error {
// nodePathForKey calculates the node file path for supplied key
func (st *BlockStorage) nodePathForKey(key string) (string, error) {
// Path separators are illegal
if strings.Contains(key, "/") {
// Path separators are illegal, as directory paths
if strings.Contains(key, "/") || key == "." || key == ".." {
return "", ErrInvalidKey
}
@ -594,6 +594,10 @@ func (st *BlockStorage) nodePathForKey(key string) (string, error) {
pb := util.GetPathBuilder()
defer util.PutPathBuilder(pb)
// Append the nodepath to key
pb.AppendString(st.nodePath)
pb.AppendString(key)
// Return joined + cleaned node-path
return pb.Join(st.nodePath, key), nil
}

View file

@ -69,7 +69,6 @@ func getDiskConfig(cfg *DiskConfig) DiskConfig {
// DiskStorage is a Storage implementation that stores directly to a filesystem
type DiskStorage struct {
path string // path is the root path of this store
dots int // dots is the "dotdot" count for the root store path
bufp pools.BufferPool // bufp is the buffer pool for this DiskStorage
config DiskConfig // cfg is the supplied configuration for this store
}
@ -120,7 +119,6 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) {
// Return new DiskStorage
return &DiskStorage{
path: path,
dots: util.CountDotdots(path),
bufp: pools.NewBufferPool(config.WriteBufSize),
config: config,
}, nil
@ -282,10 +280,10 @@ func (st *DiskStorage) filepath(key string) (string, error) {
pb.AppendString(st.path)
pb.AppendString(key)
// If path is dir traversal, and traverses FURTHER
// than store root, this is an error
if util.CountDotdots(pb.StringPtr()) > st.dots {
// Check for dir traversal outside of root
if util.IsDirTraversal(st.path, pb.StringPtr()) {
return "", ErrInvalidKey
}
return pb.String(), nil
}

View file

@ -2,7 +2,6 @@ package storage
import (
"io"
"sync"
"codeberg.org/gruf/go-bytes"
"codeberg.org/gruf/go-store/util"
@ -12,14 +11,12 @@ import (
// pairs in a Go map in-memory. The map is protected by a mutex.
type MemoryStorage struct {
fs map[string][]byte
mu sync.Mutex
}
// OpenMemory opens a new MemoryStorage instance with internal map of 'size'.
func OpenMemory(size int) *MemoryStorage {
return &MemoryStorage{
fs: make(map[string][]byte, size),
mu: sync.Mutex{},
}
}
@ -30,33 +27,19 @@ func (st *MemoryStorage) Clean() error {
// ReadBytes implements Storage.ReadBytes().
func (st *MemoryStorage) ReadBytes(key string) ([]byte, error) {
// Safely check store
st.mu.Lock()
b, ok := st.fs[key]
st.mu.Unlock()
// Return early if not exist
if !ok {
return nil, ErrNotFound
}
// Create return copy
return bytes.Copy(b), nil
}
// ReadStream implements Storage.ReadStream().
func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) {
// Safely check store
st.mu.Lock()
b, ok := st.fs[key]
st.mu.Unlock()
// Return early if not exist
if !ok {
return nil, ErrNotFound
}
// Create io.ReadCloser from 'b' copy
b = bytes.Copy(b)
r := bytes.NewReader(b)
return util.NopReadCloser(r), nil
@ -64,68 +47,43 @@ func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) {
// WriteBytes implements Storage.WriteBytes().
func (st *MemoryStorage) WriteBytes(key string, b []byte) error {
// Safely check store
st.mu.Lock()
_, ok := st.fs[key]
// Check for already exist
if ok {
st.mu.Unlock()
return ErrAlreadyExists
}
// Write + unlock
st.fs[key] = bytes.Copy(b)
st.mu.Unlock()
return nil
}
// WriteStream implements Storage.WriteStream().
func (st *MemoryStorage) WriteStream(key string, r io.Reader) error {
// Read all from reader
b, err := io.ReadAll(r)
if err != nil {
return err
}
// Write to storage
return st.WriteBytes(key, b)
}
// Stat implements Storage.Stat().
func (st *MemoryStorage) Stat(key string) (bool, error) {
st.mu.Lock()
_, ok := st.fs[key]
st.mu.Unlock()
return ok, nil
}
// Remove implements Storage.Remove().
func (st *MemoryStorage) Remove(key string) error {
// Safely check store
st.mu.Lock()
_, ok := st.fs[key]
// Check in store
if !ok {
st.mu.Unlock()
return ErrNotFound
}
// Delete + unlock
delete(st.fs, key)
st.mu.Unlock()
return nil
}
// WalkKeys implements Storage.WalkKeys().
func (st *MemoryStorage) WalkKeys(opts WalkKeysOptions) error {
// Safely walk storage keys
st.mu.Lock()
for key := range st.fs {
opts.WalkFn(entry(key))
}
st.mu.Unlock()
return nil
}

View file

@ -9,14 +9,22 @@ import (
"codeberg.org/gruf/go-fastpath"
)
var dotdot = "../"
// IsDirTraversal will check if rootPlusPath is a dir traversal outside of root,
// assuming that both are cleaned and that rootPlusPath is path.Join(root, somePath)
func IsDirTraversal(root string, rootPlusPath string) bool {
switch {
// Root is $PWD, check for traversal out of
case root == ".":
return strings.HasPrefix(rootPlusPath, "../")
// CountDotdots returns the number of "dot-dots" (../) in a cleaned filesystem path
func CountDotdots(path string) int {
if !strings.HasSuffix(path, dotdot) {
return 0
// The path MUST be prefixed by root
case !strings.HasPrefix(rootPlusPath, root):
return true
// In all other cases, check not equal
default:
return len(root) == len(rootPlusPath)
}
return strings.Count(path, dotdot)
}
// WalkDir traverses the dir tree of the supplied path, performing the supplied walkFn on each entry