fs.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. package image
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path/filepath"
  7. "sync"
  8. "github.com/Sirupsen/logrus"
  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)), 0700); 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)), 0700); 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.Hex())
  52. }
  53. func (s *fs) metadataDir(dgst digest.Digest) string {
  54. return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
  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 := ioutil.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.NewDigestFromHex(string(digest.Canonical), v.Name())
  67. if err := dgst.Validate(); err != nil {
  68. logrus.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 := ioutil.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, 0600); 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. if err := os.Remove(s.contentFile(dgst)); err != nil {
  115. return err
  116. }
  117. return nil
  118. }
  119. // SetMetadata sets metadata for a given ID. It fails if there's no base file.
  120. func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error {
  121. s.Lock()
  122. defer s.Unlock()
  123. if _, err := s.get(dgst); err != nil {
  124. return err
  125. }
  126. baseDir := filepath.Join(s.metadataDir(dgst))
  127. if err := os.MkdirAll(baseDir, 0700); err != nil {
  128. return err
  129. }
  130. return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0600)
  131. }
  132. // GetMetadata returns metadata for a given digest.
  133. func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) {
  134. s.RLock()
  135. defer s.RUnlock()
  136. if _, err := s.get(dgst); err != nil {
  137. return nil, err
  138. }
  139. bytes, err := ioutil.ReadFile(filepath.Join(s.metadataDir(dgst), key))
  140. if err != nil {
  141. return nil, errors.Wrap(err, "failed to read metadata")
  142. }
  143. return bytes, nil
  144. }
  145. // DeleteMetadata removes the metadata associated with a digest.
  146. func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error {
  147. s.Lock()
  148. defer s.Unlock()
  149. return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key))
  150. }