github.com/portworx/docker@v1.12.1/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  	"github.com/docker/docker/pkg/ioutils"
    13  )
    14  
    15  // IDWalkFunc is function called by StoreBackend.Walk
    16  type IDWalkFunc func(id ID) error
    17  
    18  // StoreBackend provides interface for image.Store persistence
    19  type StoreBackend interface {
    20  	Walk(f IDWalkFunc) error
    21  	Get(id ID) ([]byte, error)
    22  	Set(data []byte) (ID, error)
    23  	Delete(id ID) error
    24  	SetMetadata(id ID, key string, data []byte) error
    25  	GetMetadata(id ID, key string) ([]byte, error)
    26  	DeleteMetadata(id ID, 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, err
    51  	}
    52  	if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0700); err != nil {
    53  		return nil, err
    54  	}
    55  	return s, nil
    56  }
    57  
    58  func (s *fs) contentFile(id ID) string {
    59  	dgst := digest.Digest(id)
    60  	return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Hex())
    61  }
    62  
    63  func (s *fs) metadataDir(id ID) string {
    64  	dgst := digest.Digest(id)
    65  	return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Hex())
    66  }
    67  
    68  // Walk calls the supplied callback for each image ID in the storage backend.
    69  func (s *fs) Walk(f IDWalkFunc) error {
    70  	// Only Canonical digest (sha256) is currently supported
    71  	s.RLock()
    72  	dir, err := ioutil.ReadDir(filepath.Join(s.root, contentDirName, string(digest.Canonical)))
    73  	s.RUnlock()
    74  	if err != nil {
    75  		return err
    76  	}
    77  	for _, v := range dir {
    78  		dgst := digest.NewDigestFromHex(string(digest.Canonical), v.Name())
    79  		if err := dgst.Validate(); err != nil {
    80  			logrus.Debugf("Skipping invalid digest %s: %s", dgst, err)
    81  			continue
    82  		}
    83  		if err := f(ID(dgst)); err != nil {
    84  			return err
    85  		}
    86  	}
    87  	return nil
    88  }
    89  
    90  // Get returns the content stored under a given ID.
    91  func (s *fs) Get(id ID) ([]byte, error) {
    92  	s.RLock()
    93  	defer s.RUnlock()
    94  
    95  	return s.get(id)
    96  }
    97  
    98  func (s *fs) get(id ID) ([]byte, error) {
    99  	content, err := ioutil.ReadFile(s.contentFile(id))
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	// todo: maybe optional
   105  	if ID(digest.FromBytes(content)) != id {
   106  		return nil, fmt.Errorf("failed to verify image: %v", id)
   107  	}
   108  
   109  	return content, nil
   110  }
   111  
   112  // Set stores content under a given ID.
   113  func (s *fs) Set(data []byte) (ID, error) {
   114  	s.Lock()
   115  	defer s.Unlock()
   116  
   117  	if len(data) == 0 {
   118  		return "", fmt.Errorf("Invalid empty data")
   119  	}
   120  
   121  	id := ID(digest.FromBytes(data))
   122  	if err := ioutils.AtomicWriteFile(s.contentFile(id), data, 0600); err != nil {
   123  		return "", err
   124  	}
   125  
   126  	return id, nil
   127  }
   128  
   129  // Delete removes content and metadata files associated with the ID.
   130  func (s *fs) Delete(id ID) error {
   131  	s.Lock()
   132  	defer s.Unlock()
   133  
   134  	if err := os.RemoveAll(s.metadataDir(id)); err != nil {
   135  		return err
   136  	}
   137  	if err := os.Remove(s.contentFile(id)); err != nil {
   138  		return err
   139  	}
   140  	return nil
   141  }
   142  
   143  // SetMetadata sets metadata for a given ID. It fails if there's no base file.
   144  func (s *fs) SetMetadata(id ID, key string, data []byte) error {
   145  	s.Lock()
   146  	defer s.Unlock()
   147  	if _, err := s.get(id); err != nil {
   148  		return err
   149  	}
   150  
   151  	baseDir := filepath.Join(s.metadataDir(id))
   152  	if err := os.MkdirAll(baseDir, 0700); err != nil {
   153  		return err
   154  	}
   155  	return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(id), key), data, 0600)
   156  }
   157  
   158  // GetMetadata returns metadata for a given ID.
   159  func (s *fs) GetMetadata(id ID, key string) ([]byte, error) {
   160  	s.RLock()
   161  	defer s.RUnlock()
   162  
   163  	if _, err := s.get(id); err != nil {
   164  		return nil, err
   165  	}
   166  	return ioutil.ReadFile(filepath.Join(s.metadataDir(id), key))
   167  }
   168  
   169  // DeleteMetadata removes the metadata associated with an ID.
   170  func (s *fs) DeleteMetadata(id ID, key string) error {
   171  	s.Lock()
   172  	defer s.Unlock()
   173  
   174  	return os.RemoveAll(filepath.Join(s.metadataDir(id), key))
   175  }