fs.go 4.1 KB

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