Tizen_4.0 base
[platform/upstream/docker-engine.git] / image / fs.go
1 package image
2
3 import (
4         "fmt"
5         "io/ioutil"
6         "os"
7         "path/filepath"
8         "sync"
9
10         "github.com/Sirupsen/logrus"
11         "github.com/docker/docker/pkg/ioutils"
12         "github.com/opencontainers/go-digest"
13         "github.com/pkg/errors"
14 )
15
16 // DigestWalkFunc is function called by StoreBackend.Walk
17 type DigestWalkFunc func(id digest.Digest) error
18
19 // StoreBackend provides interface for image.Store persistence
20 type 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.
31 type fs struct {
32         sync.RWMutex
33         root string
34 }
35
36 const (
37         contentDirName  = "content"
38         metadataDirName = "metadata"
39 )
40
41 // NewFSStoreBackend returns new filesystem based backend for image.Store
42 func NewFSStoreBackend(root string) (StoreBackend, error) {
43         return newFSStore(root)
44 }
45
46 func 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
59 func (s *fs) contentFile(dgst digest.Digest) string {
60         return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
61 }
62
63 func (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.
68 func (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.
90 func (s *fs) Get(dgst digest.Digest) ([]byte, error) {
91         s.RLock()
92         defer s.RUnlock()
93
94         return s.get(dgst)
95 }
96
97 func (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.
112 func (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.
129 func (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         if err := os.Remove(s.contentFile(dgst)); err != nil {
137                 return err
138         }
139         return nil
140 }
141
142 // SetMetadata sets metadata for a given ID. It fails if there's no base file.
143 func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error {
144         s.Lock()
145         defer s.Unlock()
146         if _, err := s.get(dgst); err != nil {
147                 return err
148         }
149
150         baseDir := filepath.Join(s.metadataDir(dgst))
151         if err := os.MkdirAll(baseDir, 0700); err != nil {
152                 return err
153         }
154         return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0600)
155 }
156
157 // GetMetadata returns metadata for a given digest.
158 func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) {
159         s.RLock()
160         defer s.RUnlock()
161
162         if _, err := s.get(dgst); err != nil {
163                 return nil, err
164         }
165         bytes, err := ioutil.ReadFile(filepath.Join(s.metadataDir(dgst), key))
166         if err != nil {
167                 return nil, errors.Wrap(err, "failed to read metadata")
168         }
169         return bytes, nil
170 }
171
172 // DeleteMetadata removes the metadata associated with a digest.
173 func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error {
174         s.Lock()
175         defer s.Unlock()
176
177         return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key))
178 }