github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/image/fs.go (about) 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/distribution/digest" 12 ) 13 14 // IDWalkFunc is function called by StoreBackend.Walk 15 type IDWalkFunc func(id ID) error 16 17 // StoreBackend provides interface for image.Store persistence 18 type StoreBackend interface { 19 Walk(f IDWalkFunc) error 20 Get(id ID) ([]byte, error) 21 Set(data []byte) (ID, error) 22 Delete(id ID) error 23 SetMetadata(id ID, key string, data []byte) error 24 GetMetadata(id ID, key string) ([]byte, error) 25 DeleteMetadata(id ID, key string) error 26 } 27 28 // fs implements StoreBackend using the filesystem. 29 type fs struct { 30 sync.RWMutex 31 root string 32 } 33 34 const ( 35 contentDirName = "content" 36 metadataDirName = "metadata" 37 ) 38 39 // NewFSStoreBackend returns new filesystem based backend for image.Store 40 func NewFSStoreBackend(root string) (StoreBackend, error) { 41 return newFSStore(root) 42 } 43 44 func newFSStore(root string) (*fs, error) { 45 s := &fs{ 46 root: root, 47 } 48 if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0700); err != nil { 49 return nil, err 50 } 51 if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil { 52 return nil, err 53 } 54 return s, nil 55 } 56 57 func (s *fs) contentFile(id ID) string { 58 dgst := digest.Digest(id) 59 return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex()) 60 } 61 62 func (s *fs) metadataDir(id ID) string { 63 dgst := digest.Digest(id) 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 IDWalkFunc) 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(ID(dgst)); err != nil { 83 return err 84 } 85 } 86 return nil 87 } 88 89 // Get returns the content stored under a given ID. 90 func (s *fs) Get(id ID) ([]byte, error) { 91 s.RLock() 92 defer s.RUnlock() 93 94 return s.get(id) 95 } 96 97 func (s *fs) get(id ID) ([]byte, error) { 98 content, err := ioutil.ReadFile(s.contentFile(id)) 99 if err != nil { 100 return nil, err 101 } 102 103 // todo: maybe optional 104 validated, err := digest.FromBytes(content) 105 if err != nil { 106 return nil, err 107 } 108 if ID(validated) != id { 109 return nil, fmt.Errorf("failed to verify image: %v", id) 110 } 111 112 return content, nil 113 } 114 115 // Set stores content under a given ID. 116 func (s *fs) Set(data []byte) (ID, error) { 117 s.Lock() 118 defer s.Unlock() 119 120 if len(data) == 0 { 121 return "", fmt.Errorf("Invalid empty data") 122 } 123 124 dgst, err := digest.FromBytes(data) 125 if err != nil { 126 return "", err 127 } 128 id := ID(dgst) 129 filePath := s.contentFile(id) 130 tempFilePath := s.contentFile(id) + ".tmp" 131 if err := ioutil.WriteFile(tempFilePath, data, 0600); err != nil { 132 return "", err 133 } 134 if err := os.Rename(tempFilePath, filePath); err != nil { 135 return "", err 136 } 137 138 return id, nil 139 } 140 141 // Delete removes content and metadata files associated with the ID. 142 func (s *fs) Delete(id ID) error { 143 s.Lock() 144 defer s.Unlock() 145 146 if err := os.RemoveAll(s.metadataDir(id)); err != nil { 147 return err 148 } 149 if err := os.Remove(s.contentFile(id)); err != nil { 150 return err 151 } 152 return nil 153 } 154 155 // SetMetadata sets metadata for a given ID. It fails if there's no base file. 156 func (s *fs) SetMetadata(id ID, key string, data []byte) error { 157 s.Lock() 158 defer s.Unlock() 159 if _, err := s.get(id); err != nil { 160 return err 161 } 162 163 baseDir := filepath.Join(s.metadataDir(id)) 164 if err := os.MkdirAll(baseDir, 0700); err != nil { 165 return err 166 } 167 filePath := filepath.Join(s.metadataDir(id), key) 168 tempFilePath := filePath + ".tmp" 169 if err := ioutil.WriteFile(tempFilePath, data, 0600); err != nil { 170 return err 171 } 172 return os.Rename(tempFilePath, filePath) 173 } 174 175 // GetMetadata returns metadata for a given ID. 176 func (s *fs) GetMetadata(id ID, key string) ([]byte, error) { 177 s.RLock() 178 defer s.RUnlock() 179 180 if _, err := s.get(id); err != nil { 181 return nil, err 182 } 183 return ioutil.ReadFile(filepath.Join(s.metadataDir(id), key)) 184 } 185 186 // DeleteMetadata removes the metadata associated with an ID. 187 func (s *fs) DeleteMetadata(id ID, key string) error { 188 s.Lock() 189 defer s.Unlock() 190 191 return os.RemoveAll(filepath.Join(s.metadataDir(id), key)) 192 }