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