github.com/akerouanton/docker@v1.11.0-rc3/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  	if ID(digest.FromBytes(content)) != id {
   105  		return nil, fmt.Errorf("failed to verify image: %v", id)
   106  	}
   107  
   108  	return content, nil
   109  }
   110  
   111  // Set stores content under a given ID.
   112  func (s *fs) Set(data []byte) (ID, error) {
   113  	s.Lock()
   114  	defer s.Unlock()
   115  
   116  	if len(data) == 0 {
   117  		return "", fmt.Errorf("Invalid empty data")
   118  	}
   119  
   120  	id := ID(digest.FromBytes(data))
   121  	filePath := s.contentFile(id)
   122  	tempFilePath := s.contentFile(id) + ".tmp"
   123  	if err := ioutil.WriteFile(tempFilePath, data, 0600); err != nil {
   124  		return "", err
   125  	}
   126  	if err := os.Rename(tempFilePath, filePath); err != nil {
   127  		return "", err
   128  	}
   129  
   130  	return id, nil
   131  }
   132  
   133  // Delete removes content and metadata files associated with the ID.
   134  func (s *fs) Delete(id ID) error {
   135  	s.Lock()
   136  	defer s.Unlock()
   137  
   138  	if err := os.RemoveAll(s.metadataDir(id)); err != nil {
   139  		return err
   140  	}
   141  	if err := os.Remove(s.contentFile(id)); err != nil {
   142  		return err
   143  	}
   144  	return nil
   145  }
   146  
   147  // SetMetadata sets metadata for a given ID. It fails if there's no base file.
   148  func (s *fs) SetMetadata(id ID, key string, data []byte) error {
   149  	s.Lock()
   150  	defer s.Unlock()
   151  	if _, err := s.get(id); err != nil {
   152  		return err
   153  	}
   154  
   155  	baseDir := filepath.Join(s.metadataDir(id))
   156  	if err := os.MkdirAll(baseDir, 0700); err != nil {
   157  		return err
   158  	}
   159  	filePath := filepath.Join(s.metadataDir(id), key)
   160  	tempFilePath := filePath + ".tmp"
   161  	if err := ioutil.WriteFile(tempFilePath, data, 0600); err != nil {
   162  		return err
   163  	}
   164  	return os.Rename(tempFilePath, filePath)
   165  }
   166  
   167  // GetMetadata returns metadata for a given ID.
   168  func (s *fs) GetMetadata(id ID, key string) ([]byte, error) {
   169  	s.RLock()
   170  	defer s.RUnlock()
   171  
   172  	if _, err := s.get(id); err != nil {
   173  		return nil, err
   174  	}
   175  	return ioutil.ReadFile(filepath.Join(s.metadataDir(id), key))
   176  }
   177  
   178  // DeleteMetadata removes the metadata associated with an ID.
   179  func (s *fs) DeleteMetadata(id ID, key string) error {
   180  	s.Lock()
   181  	defer s.Unlock()
   182  
   183  	return os.RemoveAll(filepath.Join(s.metadataDir(id), key))
   184  }