fs.go 4.6 KB

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