image/fs.go
01ba0a93
 package image
 
 import (
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sync"
 
ea3cbd32
 	"github.com/docker/docker/pkg/ioutils"
7a855799
 	"github.com/opencontainers/go-digest"
7abeb08a
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
01ba0a93
 )
 
80522398
 // DigestWalkFunc is function called by StoreBackend.Walk
 type DigestWalkFunc func(id digest.Digest) error
01ba0a93
 
 // StoreBackend provides interface for image.Store persistence
 type StoreBackend interface {
80522398
 	Walk(f DigestWalkFunc) error
 	Get(id digest.Digest) ([]byte, error)
 	Set(data []byte) (digest.Digest, error)
 	Delete(id digest.Digest) error
 	SetMetadata(id digest.Digest, key string, data []byte) error
 	GetMetadata(id digest.Digest, key string) ([]byte, error)
 	DeleteMetadata(id digest.Digest, key string) error
01ba0a93
 }
 
 // fs implements StoreBackend using the filesystem.
 type fs struct {
 	sync.RWMutex
 	root string
 }
 
 const (
 	contentDirName  = "content"
 	metadataDirName = "metadata"
 )
 
 // NewFSStoreBackend returns new filesystem based backend for image.Store
 func NewFSStoreBackend(root string) (StoreBackend, error) {
 	return newFSStore(root)
 }
 
 func newFSStore(root string) (*fs, error) {
 	s := &fs{
 		root: root,
 	}
 	if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil {
7abeb08a
 		return nil, errors.Wrap(err, "failed to create storage backend")
01ba0a93
 	}
 	if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil {
7abeb08a
 		return nil, errors.Wrap(err, "failed to create storage backend")
01ba0a93
 	}
 	return s, nil
 }
 
80522398
 func (s *fs) contentFile(dgst digest.Digest) string {
01ba0a93
 	return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
 }
 
80522398
 func (s *fs) metadataDir(dgst digest.Digest) string {
01ba0a93
 	return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
 }
 
 // Walk calls the supplied callback for each image ID in the storage backend.
80522398
 func (s *fs) Walk(f DigestWalkFunc) error {
01ba0a93
 	// Only Canonical digest (sha256) is currently supported
 	s.RLock()
 	dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical)))
 	s.RUnlock()
 	if err != nil {
 		return err
 	}
 	for _, v := range dir {
 		dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name())
 		if err := dgst.Validate(); err != nil {
b2ec509a
 			logrus.Debugf("skipping invalid digest %s: %s", dgst, err)
01ba0a93
 			continue
 		}
80522398
 		if err := f(dgst); err != nil {
01ba0a93
 			return err
 		}
 	}
 	return nil
 }
 
80522398
 // Get returns the content stored under a given digest.
 func (s *fs) Get(dgst digest.Digest) ([]byte, error) {
01ba0a93
 	s.RLock()
 	defer s.RUnlock()
 
80522398
 	return s.get(dgst)
01ba0a93
 }
 
80522398
 func (s *fs) get(dgst digest.Digest) ([]byte, error) {
 	content, err := ioutil.ReadFile(s.contentFile(dgst))
01ba0a93
 	if err != nil {
7abeb08a
 		return nil, errors.Wrapf(err, "failed to get digest %s", dgst)
01ba0a93
 	}
 
 	// todo: maybe optional
80522398
 	if digest.FromBytes(content) != dgst {
 		return nil, fmt.Errorf("failed to verify: %v", dgst)
01ba0a93
 	}
 
 	return content, nil
 }
 
80522398
 // Set stores content by checksum.
 func (s *fs) Set(data []byte) (digest.Digest, error) {
01ba0a93
 	s.Lock()
 	defer s.Unlock()
 
 	if len(data) == 0 {
b2ec509a
 		return "", fmt.Errorf("invalid empty data")
01ba0a93
 	}
 
80522398
 	dgst := digest.FromBytes(data)
 	if err := ioutils.AtomicWriteFile(s.contentFile(dgst), data, 0600); err != nil {
7abeb08a
 		return "", errors.Wrap(err, "failed to write digest data")
01ba0a93
 	}
 
80522398
 	return dgst, nil
01ba0a93
 }
 
80522398
 // Delete removes content and metadata files associated with the digest.
 func (s *fs) Delete(dgst digest.Digest) error {
01ba0a93
 	s.Lock()
 	defer s.Unlock()
 
80522398
 	if err := os.RemoveAll(s.metadataDir(dgst)); err != nil {
01ba0a93
 		return err
 	}
b4a63139
 	return os.Remove(s.contentFile(dgst))
01ba0a93
 }
 
 // SetMetadata sets metadata for a given ID. It fails if there's no base file.
80522398
 func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error {
01ba0a93
 	s.Lock()
 	defer s.Unlock()
80522398
 	if _, err := s.get(dgst); err != nil {
01ba0a93
 		return err
 	}
 
80522398
 	baseDir := filepath.Join(s.metadataDir(dgst))
01ba0a93
 	if err := os.MkdirAll(baseDir, 0700); err != nil {
 		return err
 	}
80522398
 	return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0600)
01ba0a93
 }
 
80522398
 // GetMetadata returns metadata for a given digest.
 func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) {
01ba0a93
 	s.RLock()
 	defer s.RUnlock()
 
80522398
 	if _, err := s.get(dgst); err != nil {
01ba0a93
 		return nil, err
 	}
7abeb08a
 	bytes, err := ioutil.ReadFile(filepath.Join(s.metadataDir(dgst), key))
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to read metadata")
 	}
 	return bytes, nil
01ba0a93
 }
 
80522398
 // DeleteMetadata removes the metadata associated with a digest.
 func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error {
01ba0a93
 	s.Lock()
 	defer s.Unlock()
 
80522398
 	return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key))
01ba0a93
 }