mirror of
				https://github.com/distribution/distribution.git
				synced 2025-10-31 17:35:53 +00:00 
			
		
		
		
	This chnage prevents a crash when moving from a non-existent directory that has a file as a parent. To prevent this, we simply check that the node is a directory and throws an error if it is not. Signed-off-by: Stephen J Day <stephen.day@docker.com>
		
			
				
	
	
		
			339 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			339 lines
		
	
	
		
			6.0 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
 | |
| 	}
 | |
| 
 | |
| 	spd, ok := sp.(*dir)
 | |
| 	if !ok {
 | |
| 		return errIsNotDir // paranoid.
 | |
| 	}
 | |
| 
 | |
| 	s, ok := spd.children[srcFilename]
 | |
| 	if !ok {
 | |
| 		return errNotExists
 | |
| 	}
 | |
| 
 | |
| 	delete(spd.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, "/")
 | |
| }
 |