2021-04-08 13:53:25 +00:00
|
|
|
package fstree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/sha256"
|
|
|
|
"errors"
|
2021-10-19 16:20:53 +00:00
|
|
|
"fmt"
|
2021-06-28 14:01:31 +00:00
|
|
|
"io/fs"
|
2021-04-08 13:53:25 +00:00
|
|
|
"os"
|
2021-10-19 16:20:53 +00:00
|
|
|
"path/filepath"
|
2021-04-06 10:56:06 +00:00
|
|
|
"strings"
|
2021-04-08 13:53:25 +00:00
|
|
|
|
2022-07-05 14:07:40 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/local_object_storage/blobstor/common"
|
2021-07-28 16:19:33 +00:00
|
|
|
"github.com/nspcc-dev/neofs-node/pkg/util"
|
2021-11-10 07:08:33 +00:00
|
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
2022-05-31 17:00:41 +00:00
|
|
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
2021-04-08 13:53:25 +00:00
|
|
|
)
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// FSTree represents an object storage as a filesystem tree.
|
2021-04-08 13:53:25 +00:00
|
|
|
type FSTree struct {
|
|
|
|
Info
|
|
|
|
|
|
|
|
Depth int
|
|
|
|
DirNameLen int
|
|
|
|
}
|
|
|
|
|
|
|
|
// Info groups the information about file storage.
|
|
|
|
type Info struct {
|
|
|
|
// Permission bits of the root directory.
|
2021-06-28 14:01:31 +00:00
|
|
|
Permissions fs.FileMode
|
2021-04-08 13:53:25 +00:00
|
|
|
|
|
|
|
// Full path to the root directory.
|
|
|
|
RootPath string
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
// DirNameLen is how many bytes is used to group keys into directories.
|
|
|
|
DirNameLen = 1 // in bytes
|
|
|
|
// MaxDepth is maximum depth of nested directories.
|
|
|
|
MaxDepth = (sha256.Size - 1) / DirNameLen
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrFileNotFound is returned when file is missing.
|
|
|
|
var ErrFileNotFound = errors.New("file not found")
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
func stringifyAddress(addr oid.Address) string {
|
|
|
|
return addr.Object().EncodeToString() + "." + addr.Container().EncodeToString()
|
2021-05-19 14:53:51 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
func addressFromString(s string) (*oid.Address, error) {
|
2021-05-19 14:53:51 +00:00
|
|
|
ss := strings.SplitN(s, ".", 2)
|
|
|
|
if len(ss) != 2 {
|
|
|
|
return nil, errors.New("invalid address")
|
|
|
|
}
|
2021-04-08 13:53:25 +00:00
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
var obj oid.ID
|
|
|
|
if err := obj.DecodeString(ss[0]); err != nil {
|
2021-05-19 14:53:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
var cnr cid.ID
|
|
|
|
if err := cnr.DecodeString(ss[1]); err != nil {
|
2021-05-19 14:53:51 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
var addr oid.Address
|
|
|
|
addr.SetObject(obj)
|
|
|
|
addr.SetContainer(cnr)
|
2021-05-19 14:53:51 +00:00
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
return &addr, nil
|
2021-04-08 13:53:25 +00:00
|
|
|
}
|
|
|
|
|
2022-01-20 15:53:29 +00:00
|
|
|
// IterationPrm contains iteraction parameters.
|
|
|
|
type IterationPrm struct {
|
2022-05-31 17:00:41 +00:00
|
|
|
handler func(addr oid.Address, data []byte) error
|
2022-01-20 15:53:29 +00:00
|
|
|
ignoreErrors bool
|
2022-06-21 07:52:03 +00:00
|
|
|
errorHandler func(oid.Address, error) error
|
2022-05-31 14:11:48 +00:00
|
|
|
lazyHandler func(oid.Address, func() ([]byte, error)) error
|
2022-01-20 15:53:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WithHandler sets a function to call on each object.
|
2022-05-23 14:15:16 +00:00
|
|
|
func (p *IterationPrm) WithHandler(f func(addr oid.Address, data []byte) error) {
|
2022-01-20 15:53:29 +00:00
|
|
|
p.handler = f
|
|
|
|
}
|
|
|
|
|
2022-05-31 14:11:48 +00:00
|
|
|
// WithLazyHandler sets a function to call on each object.
|
|
|
|
// Second callback parameter opens file and reads all data to a buffer.
|
|
|
|
// File is not opened at all unless this callback is invoked.
|
|
|
|
func (p *IterationPrm) WithLazyHandler(f func(oid.Address, func() ([]byte, error)) error) {
|
|
|
|
p.lazyHandler = f
|
|
|
|
}
|
|
|
|
|
2022-01-20 15:53:29 +00:00
|
|
|
// WithIgnoreErrors sets a flag indicating whether errors should be ignored.
|
2022-05-23 14:15:16 +00:00
|
|
|
func (p *IterationPrm) WithIgnoreErrors(ignore bool) {
|
2022-01-20 15:53:29 +00:00
|
|
|
p.ignoreErrors = ignore
|
|
|
|
}
|
|
|
|
|
2022-06-21 07:52:03 +00:00
|
|
|
// WithErrorHandler sets error handler for objects that cannot be read or unmarshaled.
|
|
|
|
func (p *IterationPrm) WithErrorHandler(f func(oid.Address, error) error) {
|
|
|
|
p.errorHandler = f
|
|
|
|
}
|
|
|
|
|
2021-04-06 10:56:06 +00:00
|
|
|
// Iterate iterates over all stored objects.
|
2022-05-23 14:15:16 +00:00
|
|
|
func (t *FSTree) Iterate(prm IterationPrm) error {
|
2022-01-20 15:53:29 +00:00
|
|
|
return t.iterate(0, []string{t.RootPath}, prm)
|
2021-04-06 10:56:06 +00:00
|
|
|
}
|
|
|
|
|
2022-05-23 14:15:16 +00:00
|
|
|
func (t *FSTree) iterate(depth int, curPath []string, prm IterationPrm) error {
|
2021-04-06 10:56:06 +00:00
|
|
|
curName := strings.Join(curPath[1:], "")
|
2022-02-02 13:28:08 +00:00
|
|
|
des, err := os.ReadDir(filepath.Join(curPath...))
|
2021-04-06 10:56:06 +00:00
|
|
|
if err != nil {
|
2022-01-20 15:53:29 +00:00
|
|
|
if prm.ignoreErrors {
|
|
|
|
return nil
|
|
|
|
}
|
2021-04-06 10:56:06 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
isLast := depth >= t.Depth
|
|
|
|
l := len(curPath)
|
|
|
|
curPath = append(curPath, "")
|
|
|
|
|
|
|
|
for i := range des {
|
|
|
|
curPath[l] = des[i].Name()
|
|
|
|
|
|
|
|
if !isLast && des[i].IsDir() {
|
2022-01-20 15:53:29 +00:00
|
|
|
err := t.iterate(depth+1, curPath, prm)
|
2021-04-06 10:56:06 +00:00
|
|
|
if err != nil {
|
2022-01-20 15:53:29 +00:00
|
|
|
// Must be error from handler in case errors are ignored.
|
|
|
|
// Need to report.
|
2021-04-06 10:56:06 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 14:53:51 +00:00
|
|
|
if depth != t.Depth {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, err := addressFromString(curName + des[i].Name())
|
2021-04-06 10:56:06 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-05-31 14:11:48 +00:00
|
|
|
if prm.lazyHandler != nil {
|
|
|
|
err = prm.lazyHandler(*addr, func() ([]byte, error) {
|
|
|
|
return os.ReadFile(filepath.Join(curPath...))
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
var data []byte
|
|
|
|
data, err = os.ReadFile(filepath.Join(curPath...))
|
|
|
|
if err != nil {
|
|
|
|
if prm.ignoreErrors {
|
2022-06-21 07:52:03 +00:00
|
|
|
if prm.errorHandler != nil {
|
|
|
|
return prm.errorHandler(*addr, err)
|
|
|
|
}
|
2022-05-31 14:11:48 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
return err
|
2022-01-20 15:53:29 +00:00
|
|
|
}
|
2022-05-31 14:11:48 +00:00
|
|
|
err = prm.handler(*addr, data)
|
2021-04-06 10:56:06 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 14:11:48 +00:00
|
|
|
if err != nil {
|
2021-04-06 10:56:06 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-31 17:00:41 +00:00
|
|
|
func (t *FSTree) treePath(addr oid.Address) string {
|
2021-04-08 13:53:25 +00:00
|
|
|
sAddr := stringifyAddress(addr)
|
|
|
|
|
|
|
|
dirs := make([]string, 0, t.Depth+1+1) // 1 for root, 1 for file
|
|
|
|
dirs = append(dirs, t.RootPath)
|
|
|
|
|
|
|
|
for i := 0; i < t.Depth; i++ {
|
|
|
|
dirs = append(dirs, sAddr[:t.DirNameLen])
|
|
|
|
sAddr = sAddr[t.DirNameLen:]
|
|
|
|
}
|
|
|
|
|
|
|
|
dirs = append(dirs, sAddr)
|
|
|
|
|
2022-02-02 13:28:08 +00:00
|
|
|
return filepath.Join(dirs...)
|
2021-04-08 13:53:25 +00:00
|
|
|
}
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// Delete removes the object with the specified address from the storage.
|
2022-05-31 17:00:41 +00:00
|
|
|
func (t *FSTree) Delete(addr oid.Address) error {
|
2021-04-08 13:53:25 +00:00
|
|
|
p, err := t.Exists(addr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return os.Remove(p)
|
|
|
|
}
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// Exists returns the path to the file with object contents if it exists in the storage
|
2021-04-08 13:53:25 +00:00
|
|
|
// and an error otherwise.
|
2022-05-31 17:00:41 +00:00
|
|
|
func (t *FSTree) Exists(addr oid.Address) (string, error) {
|
2021-04-08 13:53:25 +00:00
|
|
|
p := t.treePath(addr)
|
|
|
|
|
|
|
|
_, err := os.Stat(p)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = ErrFileNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return p, err
|
|
|
|
}
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// Put puts an object in the storage.
|
2022-07-06 13:41:35 +00:00
|
|
|
func (t *FSTree) Put(prm common.PutPrm) (common.PutRes, error) {
|
|
|
|
p := t.treePath(prm.Address)
|
2021-04-08 13:53:25 +00:00
|
|
|
|
2022-02-02 13:28:08 +00:00
|
|
|
if err := util.MkdirAllX(filepath.Dir(p), t.Permissions); err != nil {
|
2022-07-06 13:41:35 +00:00
|
|
|
return common.PutRes{}, err
|
2021-04-08 13:53:25 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 13:41:35 +00:00
|
|
|
return common.PutRes{}, os.WriteFile(p, prm.RawData, t.Permissions)
|
2021-04-08 13:53:25 +00:00
|
|
|
}
|
|
|
|
|
2022-04-28 10:19:03 +00:00
|
|
|
// PutStream puts executes handler on a file opened for write.
|
2022-05-31 17:00:41 +00:00
|
|
|
func (t *FSTree) PutStream(addr oid.Address, handler func(*os.File) error) error {
|
2022-04-28 10:19:03 +00:00
|
|
|
p := t.treePath(addr)
|
|
|
|
|
|
|
|
if err := util.MkdirAllX(filepath.Dir(p), t.Permissions); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, t.Permissions)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
return handler(f)
|
|
|
|
}
|
|
|
|
|
2022-04-21 11:28:05 +00:00
|
|
|
// Get returns an object from the storage by address.
|
2022-07-05 14:07:40 +00:00
|
|
|
func (t *FSTree) Get(prm common.GetPrm) ([]byte, error) {
|
|
|
|
p := t.treePath(prm.Address)
|
2021-04-08 13:53:25 +00:00
|
|
|
|
|
|
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
|
|
return nil, ErrFileNotFound
|
|
|
|
}
|
|
|
|
|
2021-06-28 14:01:31 +00:00
|
|
|
return os.ReadFile(p)
|
2021-04-08 13:53:25 +00:00
|
|
|
}
|
2021-10-19 16:20:53 +00:00
|
|
|
|
|
|
|
// NumberOfObjects walks the file tree rooted at FSTree's root
|
|
|
|
// and returns number of stored objects.
|
|
|
|
func (t *FSTree) NumberOfObjects() (uint64, error) {
|
|
|
|
var counter uint64
|
|
|
|
|
|
|
|
// it is simpler to just consider every file
|
|
|
|
// that is not directory as an object
|
|
|
|
err := filepath.WalkDir(t.RootPath,
|
|
|
|
func(_ string, d fs.DirEntry, _ error) error {
|
|
|
|
if !d.IsDir() {
|
|
|
|
counter++
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("could not walk through %s directory: %w", t.RootPath, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return counter, nil
|
|
|
|
}
|