1package image // import "github.com/docker/docker/image"
2
3import (
4	"fmt"
5	"io/ioutil"
6	"os"
7	"path/filepath"
8	"sync"
9
10	"github.com/docker/docker/pkg/ioutils"
11	"github.com/opencontainers/go-digest"
12	"github.com/pkg/errors"
13	"github.com/sirupsen/logrus"
14)
15
16// DigestWalkFunc is function called by StoreBackend.Walk
17type DigestWalkFunc func(id digest.Digest) error
18
19// StoreBackend provides interface for image.Store persistence
20type StoreBackend interface {
21	Walk(f DigestWalkFunc) error
22	Get(id digest.Digest) ([]byte, error)
23	Set(data []byte) (digest.Digest, error)
24	Delete(id digest.Digest) error
25	SetMetadata(id digest.Digest, key string, data []byte) error
26	GetMetadata(id digest.Digest, key string) ([]byte, error)
27	DeleteMetadata(id digest.Digest, key string) error
28}
29
30// fs implements StoreBackend using the filesystem.
31type fs struct {
32	sync.RWMutex
33	root string
34}
35
36const (
37	contentDirName  = "content"
38	metadataDirName = "metadata"
39)
40
41// NewFSStoreBackend returns new filesystem based backend for image.Store
42func NewFSStoreBackend(root string) (StoreBackend, error) {
43	return newFSStore(root)
44}
45
46func newFSStore(root string) (*fs, error) {
47	s := &fs{
48		root: root,
49	}
50	if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil {
51		return nil, errors.Wrap(err, "failed to create storage backend")
52	}
53	if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil {
54		return nil, errors.Wrap(err, "failed to create storage backend")
55	}
56	return s, nil
57}
58
59func (s *fs) contentFile(dgst digest.Digest) string {
60	return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
61}
62
63func (s *fs) metadataDir(dgst digest.Digest) string {
64	return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
65}
66
67// Walk calls the supplied callback for each image ID in the storage backend.
68func (s *fs) Walk(f DigestWalkFunc) error {
69	// Only Canonical digest (sha256) is currently supported
70	s.RLock()
71	dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical)))
72	s.RUnlock()
73	if err != nil {
74		return err
75	}
76	for _, v := range dir {
77		dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name())
78		if err := dgst.Validate(); err != nil {
79			logrus.Debugf("skipping invalid digest %s: %s", dgst, err)
80			continue
81		}
82		if err := f(dgst); err != nil {
83			return err
84		}
85	}
86	return nil
87}
88
89// Get returns the content stored under a given digest.
90func (s *fs) Get(dgst digest.Digest) ([]byte, error) {
91	s.RLock()
92	defer s.RUnlock()
93
94	return s.get(dgst)
95}
96
97func (s *fs) get(dgst digest.Digest) ([]byte, error) {
98	content, err := ioutil.ReadFile(s.contentFile(dgst))
99	if err != nil {
100		return nil, errors.Wrapf(err, "failed to get digest %s", dgst)
101	}
102
103	// todo: maybe optional
104	if digest.FromBytes(content) != dgst {
105		return nil, fmt.Errorf("failed to verify: %v", dgst)
106	}
107
108	return content, nil
109}
110
111// Set stores content by checksum.
112func (s *fs) Set(data []byte) (digest.Digest, error) {
113	s.Lock()
114	defer s.Unlock()
115
116	if len(data) == 0 {
117		return "", fmt.Errorf("invalid empty data")
118	}
119
120	dgst := digest.FromBytes(data)
121	if err := ioutils.AtomicWriteFile(s.contentFile(dgst), data, 0600); err != nil {
122		return "", errors.Wrap(err, "failed to write digest data")
123	}
124
125	return dgst, nil
126}
127
128// Delete removes content and metadata files associated with the digest.
129func (s *fs) Delete(dgst digest.Digest) error {
130	s.Lock()
131	defer s.Unlock()
132
133	if err := os.RemoveAll(s.metadataDir(dgst)); err != nil {
134		return err
135	}
136	return os.Remove(s.contentFile(dgst))
137}
138
139// SetMetadata sets metadata for a given ID. It fails if there's no base file.
140func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error {
141	s.Lock()
142	defer s.Unlock()
143	if _, err := s.get(dgst); err != nil {
144		return err
145	}
146
147	baseDir := filepath.Join(s.metadataDir(dgst))
148	if err := os.MkdirAll(baseDir, 0700); err != nil {
149		return err
150	}
151	return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0600)
152}
153
154// GetMetadata returns metadata for a given digest.
155func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) {
156	s.RLock()
157	defer s.RUnlock()
158
159	if _, err := s.get(dgst); err != nil {
160		return nil, err
161	}
162	bytes, err := ioutil.ReadFile(filepath.Join(s.metadataDir(dgst), key))
163	if err != nil {
164		return nil, errors.Wrap(err, "failed to read metadata")
165	}
166	return bytes, nil
167}
168
169// DeleteMetadata removes the metadata associated with a digest.
170func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error {
171	s.Lock()
172	defer s.Unlock()
173
174	return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key))
175}
176