storj/cmd/uplink/ulfs/local_backend_mem.go
Jeff Wendling 89ccfe2dd7 cmd/uplink: fix recursive copy and improve tests
recursive copy had a bug with relative local paths.

this fixes that bug and changes the test framework
to use more of the code that actually runs in uplink
and only mocks out the direct interaction with the
operating system.

Change-Id: I9da2a80bfda8f86a8d05879b87171f299f759c7e
2022-05-11 15:17:16 -04:00

311 lines
6.7 KiB
Go

// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
package ulfs
import (
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/zeebo/errs"
)
// LocalBackendMem implements LocalBackend with memory backed files.
type LocalBackendMem struct {
root *memDir
cwd *memDir
}
// NewLocalBackendMem creates a new LocalBackendMem.
func NewLocalBackendMem() *LocalBackendMem {
return &LocalBackendMem{
root: newMemDir("/"),
cwd: newMemDir(""),
}
}
func (l *LocalBackendMem) openRoot(name string) (string, *memDir) {
if strings.HasPrefix(name, string(filepath.Separator)) {
return name, l.root
} else if name == "." {
return name[1:], l.cwd
} else if strings.HasPrefix(name, "./") {
return name[2:], l.cwd
}
return name, l.cwd
}
func (l *LocalBackendMem) openParent(name string) (*memDir, string, error) {
dir := filepath.Dir(name)
fh, err := l.Open(dir)
if err != nil {
return nil, "", err
}
md, ok := fh.(*memDir)
if !ok {
return nil, "", errs.New("parent not a directory: %q", dir)
}
return md, filepath.Base(name), nil
}
// Create creates a new file for the given name.
func (l *LocalBackendMem) Create(name string) (LocalBackendFile, error) {
name = filepath.Clean(name)
md, base, err := l.openParent(name)
if err != nil {
return nil, err
}
if fh, ok := md.children[base]; ok {
if _, ok := fh.(*memDir); ok {
return nil, errs.New("file already exists: %q", name)
}
}
mf := newMemFile(name)
md.children[base] = mf
return mf, nil
}
// MkdirAll recursively creates directories to make name a directory.
func (l *LocalBackendMem) MkdirAll(name string, perm os.FileMode) error {
name = filepath.Clean(name)
name, root := l.openRoot(name)
return iterateComponents(name, func(name, ent string) error {
fh, ok := root.children[ent]
if !ok {
fh = newMemDir(name)
root.children[ent] = fh
}
md, ok := fh.(*memDir)
if !ok {
return errs.New("file already exists: %q", name)
}
root = md
return nil
})
}
// Open opens the file with the given name.
func (l *LocalBackendMem) Open(name string) (LocalBackendFile, error) {
name = filepath.Clean(name)
var root LocalBackendFile
name, root = l.openRoot(name)
err := iterateComponents(name, func(name, ent string) error {
md, ok := root.(*memDir)
if !ok {
return errs.New("not a directory: %q", name)
}
fh, ok := md.children[ent]
if !ok {
return os.ErrNotExist
}
root = fh
return nil
})
if err != nil {
return nil, err
}
return root, nil
}
// Remove deletes the file with the given name.
func (l *LocalBackendMem) Remove(name string) error {
name = filepath.Clean(name)
md, base, err := l.openParent(name)
if err != nil {
return err
}
if _, ok := md.children[base]; !ok {
return errs.New("file does not exists: %q", name)
}
delete(md.children, base)
return nil
}
// Rename causes the file at oldname to be moved to newname.
func (l *LocalBackendMem) Rename(oldname, newname string) error {
oldname = filepath.Clean(oldname)
newname = filepath.Clean(newname)
omd, obase, err := l.openParent(oldname)
if err != nil {
return err
}
nmd, nbase, err := l.openParent(newname)
if err != nil {
return err
}
f, ok := omd.children[obase]
if !ok {
return os.ErrNotExist
}
switch f := f.(type) {
case *memFile:
f.name = newname
case *memDir:
f.name = newname
}
nmd.children[nbase] = f
delete(omd.children, obase)
return nil
}
// Stat returns file info for the given name.
func (l *LocalBackendMem) Stat(name string) (os.FileInfo, error) {
fh, err := l.Open(name)
if err != nil {
return nil, err
}
return fh.Stat()
}
//
// memFile
//
type memFile struct {
name string
buf []byte
}
func newMemFile(name string) *memFile {
return &memFile{
name: name,
}
}
func (mf *memFile) String() string { return fmt.Sprintf("File[%q]", mf.name) }
func (mf *memFile) Name() string { return mf.name }
func (mf *memFile) Close() error { return nil }
func (mf *memFile) ReadAt(p []byte, off int64) (int, error) {
if off >= int64(len(mf.buf)) {
return 0, io.EOF
}
return copy(p, mf.buf[off:]), nil
}
func (mf *memFile) WriteAt(p []byte, off int64) (int, error) {
if delta := (off + int64(len(p))) - int64(len(mf.buf)); delta > 0 {
mf.buf = append(mf.buf, make([]byte, delta)...)
}
return copy(mf.buf[off:], p), nil
}
func (mf *memFile) Stat() (os.FileInfo, error) {
return (*memFileInfo)(mf), nil
}
func (mf *memFile) Readdir(n int) ([]os.FileInfo, error) {
return nil, errs.New("readdir on regular file")
}
type memFileInfo memFile
var _ os.FileInfo = (*memFileInfo)(nil)
func (mfi *memFileInfo) Name() string {
return filepath.Base((*memFile)(mfi).name)
}
func (mfi *memFileInfo) Size() int64 { return int64(len((*memFile)(mfi).buf)) }
func (mfi *memFileInfo) Mode() fs.FileMode { return 0777 }
func (mfi *memFileInfo) ModTime() time.Time { return time.Time{} }
func (mfi *memFileInfo) IsDir() bool { return false }
func (mfi *memFileInfo) Sys() interface{} { return nil }
//
// memDir
//
type memDir struct {
name string
children map[string]LocalBackendFile
}
func newMemDir(name string) *memDir {
return &memDir{
name: name,
children: make(map[string]LocalBackendFile),
}
}
var _ LocalBackendFile = (*memDir)(nil)
func (md *memDir) String() string { return fmt.Sprintf("Dir[%q, %v]", md.name, md.children) }
func (md *memDir) Name() string { return md.name }
func (md *memDir) Close() error { return nil }
func (md *memDir) ReadAt(p []byte, off int64) (int, error) {
return 0, errs.New("readat on directory")
}
func (md *memDir) WriteAt(p []byte, off int64) (int, error) {
return 0, errs.New("writeat on directory")
}
func (md *memDir) Stat() (os.FileInfo, error) {
return (*memDirInfo)(md), nil
}
func (md *memDir) Readdir(n int) ([]os.FileInfo, error) {
if n != -1 {
return nil, errs.New("can only read all entries")
}
out := make([]os.FileInfo, 0, len(md.children))
for _, child := range md.children {
info, _ := child.Stat()
out = append(out, info)
}
return out, nil
}
type memDirInfo memDir
var _ os.FileInfo = (*memDirInfo)(nil)
func (dfi *memDirInfo) Name() string {
return filepath.Base((*memDir)(dfi).name)
}
func (dfi *memDirInfo) Size() int64 { return 0 }
func (dfi *memDirInfo) Mode() fs.FileMode { return 0777 }
func (dfi *memDirInfo) ModTime() time.Time { return time.Time{} }
func (dfi *memDirInfo) IsDir() bool { return true }
func (dfi *memDirInfo) Sys() interface{} { return nil }
//
// helpers
//
func iterateComponents(name string, cb func(name, ent string) error) error {
i := 0
for i < len(name) {
part := strings.IndexByte(name[i:], filepath.Separator)
if part == -1 {
return cb(name, name[i:])
}
if err := cb(name[:i+part], name[i:i+part]); err != nil {
return err
}
i += part + 1
}
return nil
}