forked from TrueCloudLab/distribution
6e4f9a2e3e
This change is slightly more complex than previous package maves in that the package name changed. To address this, we simply always reference the package driver as storagedriver to avoid compatbility issues with existing code. While unfortunate, this can be cleaned up over time. Signed-off-by: Stephen J Day <stephen.day@docker.com>
333 lines
5.9 KiB
Go
333 lines
5.9 KiB
Go
package inmemory
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
errExists = fmt.Errorf("exists")
|
|
errNotExists = fmt.Errorf("notexists")
|
|
errIsNotDir = fmt.Errorf("notdir")
|
|
errIsDir = fmt.Errorf("isdir")
|
|
)
|
|
|
|
type node interface {
|
|
name() string
|
|
path() string
|
|
isdir() bool
|
|
modtime() time.Time
|
|
}
|
|
|
|
// dir is the central type for the memory-based storagedriver. All operations
|
|
// are dispatched from a root dir.
|
|
type dir struct {
|
|
common
|
|
|
|
// TODO(stevvooe): Use sorted slice + search.
|
|
children map[string]node
|
|
}
|
|
|
|
var _ node = &dir{}
|
|
|
|
func (d *dir) isdir() bool {
|
|
return true
|
|
}
|
|
|
|
// add places the node n into dir d.
|
|
func (d *dir) add(n node) {
|
|
if d.children == nil {
|
|
d.children = make(map[string]node)
|
|
}
|
|
|
|
d.children[n.name()] = n
|
|
d.mod = time.Now()
|
|
}
|
|
|
|
// find searches for the node, given path q in dir. If the node is found, it
|
|
// will be returned. If the node is not found, the closet existing parent. If
|
|
// the node is found, the returned (node).path() will match q.
|
|
func (d *dir) find(q string) node {
|
|
q = strings.Trim(q, "/")
|
|
i := strings.Index(q, "/")
|
|
|
|
if q == "" {
|
|
return d
|
|
}
|
|
|
|
if i == 0 {
|
|
panic("shouldn't happen, no root paths")
|
|
}
|
|
|
|
var component string
|
|
if i < 0 {
|
|
// No more path components
|
|
component = q
|
|
} else {
|
|
component = q[:i]
|
|
}
|
|
|
|
child, ok := d.children[component]
|
|
if !ok {
|
|
// Node was not found. Return p and the current node.
|
|
return d
|
|
}
|
|
|
|
if child.isdir() {
|
|
// traverse down!
|
|
q = q[i+1:]
|
|
return child.(*dir).find(q)
|
|
}
|
|
|
|
return child
|
|
}
|
|
|
|
func (d *dir) list(p string) ([]string, error) {
|
|
n := d.find(p)
|
|
|
|
if n.path() != p {
|
|
return nil, errNotExists
|
|
}
|
|
|
|
if !n.isdir() {
|
|
return nil, errIsNotDir
|
|
}
|
|
|
|
var children []string
|
|
for _, child := range n.(*dir).children {
|
|
children = append(children, child.path())
|
|
}
|
|
|
|
sort.Strings(children)
|
|
return children, nil
|
|
}
|
|
|
|
// mkfile or return the existing one. returns an error if it exists and is a
|
|
// directory. Essentially, this is open or create.
|
|
func (d *dir) mkfile(p string) (*file, error) {
|
|
n := d.find(p)
|
|
if n.path() == p {
|
|
if n.isdir() {
|
|
return nil, errIsDir
|
|
}
|
|
|
|
return n.(*file), nil
|
|
}
|
|
|
|
dirpath, filename := path.Split(p)
|
|
// Make any non-existent directories
|
|
n, err := d.mkdirs(dirpath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dd := n.(*dir)
|
|
n = &file{
|
|
common: common{
|
|
p: path.Join(dd.path(), filename),
|
|
mod: time.Now(),
|
|
},
|
|
}
|
|
|
|
dd.add(n)
|
|
return n.(*file), nil
|
|
}
|
|
|
|
// mkdirs creates any missing directory entries in p and returns the result.
|
|
func (d *dir) mkdirs(p string) (*dir, error) {
|
|
p = normalize(p)
|
|
|
|
n := d.find(p)
|
|
|
|
if !n.isdir() {
|
|
// Found something there
|
|
return nil, errIsNotDir
|
|
}
|
|
|
|
if n.path() == p {
|
|
return n.(*dir), nil
|
|
}
|
|
|
|
dd := n.(*dir)
|
|
|
|
relative := strings.Trim(strings.TrimPrefix(p, n.path()), "/")
|
|
|
|
if relative == "" {
|
|
return dd, nil
|
|
}
|
|
|
|
components := strings.Split(relative, "/")
|
|
for _, component := range components {
|
|
d, err := dd.mkdir(component)
|
|
|
|
if err != nil {
|
|
// This should actually never happen, since there are no children.
|
|
return nil, err
|
|
}
|
|
dd = d
|
|
}
|
|
|
|
return dd, nil
|
|
}
|
|
|
|
// mkdir creates a child directory under d with the given name.
|
|
func (d *dir) mkdir(name string) (*dir, error) {
|
|
if name == "" {
|
|
return nil, fmt.Errorf("invalid dirname")
|
|
}
|
|
|
|
_, ok := d.children[name]
|
|
if ok {
|
|
return nil, errExists
|
|
}
|
|
|
|
child := &dir{
|
|
common: common{
|
|
p: path.Join(d.path(), name),
|
|
mod: time.Now(),
|
|
},
|
|
}
|
|
d.add(child)
|
|
d.mod = time.Now()
|
|
|
|
return child, nil
|
|
}
|
|
|
|
func (d *dir) move(src, dst string) error {
|
|
dstDirname, _ := path.Split(dst)
|
|
|
|
dp, err := d.mkdirs(dstDirname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srcDirname, srcFilename := path.Split(src)
|
|
sp := d.find(srcDirname)
|
|
|
|
if normalize(srcDirname) != normalize(sp.path()) {
|
|
return errNotExists
|
|
}
|
|
|
|
s, ok := sp.(*dir).children[srcFilename]
|
|
if !ok {
|
|
return errNotExists
|
|
}
|
|
|
|
delete(sp.(*dir).children, srcFilename)
|
|
|
|
switch n := s.(type) {
|
|
case *dir:
|
|
n.p = dst
|
|
case *file:
|
|
n.p = dst
|
|
}
|
|
|
|
dp.add(s)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *dir) delete(p string) error {
|
|
dirname, filename := path.Split(p)
|
|
parent := d.find(dirname)
|
|
|
|
if normalize(dirname) != normalize(parent.path()) {
|
|
return errNotExists
|
|
}
|
|
|
|
if _, ok := parent.(*dir).children[filename]; !ok {
|
|
return errNotExists
|
|
}
|
|
|
|
delete(parent.(*dir).children, filename)
|
|
return nil
|
|
}
|
|
|
|
// dump outputs a primitive directory structure to stdout.
|
|
func (d *dir) dump(indent string) {
|
|
fmt.Println(indent, d.name()+"/")
|
|
|
|
for _, child := range d.children {
|
|
if child.isdir() {
|
|
child.(*dir).dump(indent + "\t")
|
|
} else {
|
|
fmt.Println(indent, child.name())
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func (d *dir) String() string {
|
|
return fmt.Sprintf("&dir{path: %v, children: %v}", d.p, d.children)
|
|
}
|
|
|
|
// file stores actual data in the fs tree. It acts like an open, seekable file
|
|
// where operations are conducted through ReadAt and WriteAt. Use it with
|
|
// SectionReader for the best effect.
|
|
type file struct {
|
|
common
|
|
data []byte
|
|
}
|
|
|
|
var _ node = &file{}
|
|
|
|
func (f *file) isdir() bool {
|
|
return false
|
|
}
|
|
|
|
func (f *file) truncate() {
|
|
f.data = f.data[:0]
|
|
}
|
|
|
|
func (f *file) sectionReader(offset int64) io.Reader {
|
|
return io.NewSectionReader(f, offset, int64(len(f.data))-offset)
|
|
}
|
|
|
|
func (f *file) ReadAt(p []byte, offset int64) (n int, err error) {
|
|
return copy(p, f.data[offset:]), nil
|
|
}
|
|
|
|
func (f *file) WriteAt(p []byte, offset int64) (n int, err error) {
|
|
off := int(offset)
|
|
if cap(f.data) < off+len(p) {
|
|
data := make([]byte, len(f.data), off+len(p))
|
|
copy(data, f.data)
|
|
f.data = data
|
|
}
|
|
|
|
f.mod = time.Now()
|
|
f.data = f.data[:off+len(p)]
|
|
|
|
return copy(f.data[off:off+len(p)], p), nil
|
|
}
|
|
|
|
func (f *file) String() string {
|
|
return fmt.Sprintf("&file{path: %q}", f.p)
|
|
}
|
|
|
|
// common provides shared fields and methods for node implementations.
|
|
type common struct {
|
|
p string
|
|
mod time.Time
|
|
}
|
|
|
|
func (c *common) name() string {
|
|
_, name := path.Split(c.p)
|
|
return name
|
|
}
|
|
|
|
func (c *common) path() string {
|
|
return c.p
|
|
}
|
|
|
|
func (c *common) modtime() time.Time {
|
|
return c.mod
|
|
}
|
|
|
|
func normalize(p string) string {
|
|
return "/" + strings.Trim(p, "/")
|
|
}
|