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