github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/image/fs.go (about)

     1  package image // import "github.com/docker/docker/image"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"github.com/containerd/log"
    11  	"github.com/docker/docker/pkg/ioutils"
    12  	"github.com/opencontainers/go-digest"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  // DigestWalkFunc is function called by StoreBackend.Walk
    17  type DigestWalkFunc func(id digest.Digest) error
    18  
    19  // StoreBackend provides interface for image.Store persistence
    20  type StoreBackend interface {
    21  	Walk(f DigestWalkFunc) error
    22  	Get(id digest.Digest) ([]byte, error)
    23  	Set(data []byte) (digest.Digest, error)
    24  	Delete(id digest.Digest) error
    25  	SetMetadata(id digest.Digest, key string, data []byte) error
    26  	GetMetadata(id digest.Digest, key string) ([]byte, error)
    27  	DeleteMetadata(id digest.Digest, key string) error
    28  }
    29  
    30  // fs implements StoreBackend using the filesystem.
    31  type fs struct {
    32  	sync.RWMutex
    33  	root string
    34  }
    35  
    36  const (
    37  	contentDirName  = "content"
    38  	metadataDirName = "metadata"
    39  )
    40  
    41  // NewFSStoreBackend returns new filesystem based backend for image.Store
    42  func NewFSStoreBackend(root string) (StoreBackend, error) {
    43  	return newFSStore(root)
    44  }
    45  
    46  func newFSStore(root string) (*fs, error) {
    47  	s := &fs{
    48  		root: root,
    49  	}
    50  	if err := os.MkdirAll(filepath.Join(root, contentDirName, string(digest.Canonical)), 0o700); err != nil {
    51  		return nil, errors.Wrap(err, "failed to create storage backend")
    52  	}
    53  	if err := os.MkdirAll(filepath.Join(root, metadataDirName, string(digest.Canonical)), 0o700); err != nil {
    54  		return nil, errors.Wrap(err, "failed to create storage backend")
    55  	}
    56  	return s, nil
    57  }
    58  
    59  func (s *fs) contentFile(dgst digest.Digest) string {
    60  	return filepath.Join(s.root, contentDirName, string(dgst.Algorithm()), dgst.Encoded())
    61  }
    62  
    63  func (s *fs) metadataDir(dgst digest.Digest) string {
    64  	return filepath.Join(s.root, metadataDirName, string(dgst.Algorithm()), dgst.Encoded())
    65  }
    66  
    67  // Walk calls the supplied callback for each image ID in the storage backend.
    68  func (s *fs) Walk(f DigestWalkFunc) error {
    69  	// Only Canonical digest (sha256) is currently supported
    70  	s.RLock()
    71  	dir, err := os.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.NewDigestFromEncoded(digest.Canonical, v.Name())
    78  		if err := dgst.Validate(); err != nil {
    79  			log.G(context.TODO()).Debugf("skipping invalid digest %s: %s", dgst, err)
    80  			continue
    81  		}
    82  		if err := f(dgst); err != nil {
    83  			return err
    84  		}
    85  	}
    86  	return nil
    87  }
    88  
    89  // Get returns the content stored under a given digest.
    90  func (s *fs) Get(dgst digest.Digest) ([]byte, error) {
    91  	s.RLock()
    92  	defer s.RUnlock()
    93  
    94  	return s.get(dgst)
    95  }
    96  
    97  func (s *fs) get(dgst digest.Digest) ([]byte, error) {
    98  	content, err := os.ReadFile(s.contentFile(dgst))
    99  	if err != nil {
   100  		return nil, errors.Wrapf(err, "failed to get digest %s", dgst)
   101  	}
   102  
   103  	// todo: maybe optional
   104  	if digest.FromBytes(content) != dgst {
   105  		return nil, fmt.Errorf("failed to verify: %v", dgst)
   106  	}
   107  
   108  	return content, nil
   109  }
   110  
   111  // Set stores content by checksum.
   112  func (s *fs) Set(data []byte) (digest.Digest, error) {
   113  	s.Lock()
   114  	defer s.Unlock()
   115  
   116  	if len(data) == 0 {
   117  		return "", fmt.Errorf("invalid empty data")
   118  	}
   119  
   120  	dgst := digest.FromBytes(data)
   121  	if err := ioutils.AtomicWriteFile(s.contentFile(dgst), data, 0o600); err != nil {
   122  		return "", errors.Wrap(err, "failed to write digest data")
   123  	}
   124  
   125  	return dgst, nil
   126  }
   127  
   128  // Delete removes content and metadata files associated with the digest.
   129  func (s *fs) Delete(dgst digest.Digest) error {
   130  	s.Lock()
   131  	defer s.Unlock()
   132  
   133  	if err := os.RemoveAll(s.metadataDir(dgst)); err != nil {
   134  		return err
   135  	}
   136  	return os.Remove(s.contentFile(dgst))
   137  }
   138  
   139  // SetMetadata sets metadata for a given ID. It fails if there's no base file.
   140  func (s *fs) SetMetadata(dgst digest.Digest, key string, data []byte) error {
   141  	s.Lock()
   142  	defer s.Unlock()
   143  	if _, err := s.get(dgst); err != nil {
   144  		return err
   145  	}
   146  
   147  	baseDir := filepath.Join(s.metadataDir(dgst))
   148  	if err := os.MkdirAll(baseDir, 0o700); err != nil {
   149  		return err
   150  	}
   151  	return ioutils.AtomicWriteFile(filepath.Join(s.metadataDir(dgst), key), data, 0o600)
   152  }
   153  
   154  // GetMetadata returns metadata for a given digest.
   155  func (s *fs) GetMetadata(dgst digest.Digest, key string) ([]byte, error) {
   156  	s.RLock()
   157  	defer s.RUnlock()
   158  
   159  	if _, err := s.get(dgst); err != nil {
   160  		return nil, err
   161  	}
   162  	bytes, err := os.ReadFile(filepath.Join(s.metadataDir(dgst), key))
   163  	if err != nil {
   164  		return nil, errors.Wrap(err, "failed to read metadata")
   165  	}
   166  	return bytes, nil
   167  }
   168  
   169  // DeleteMetadata removes the metadata associated with a digest.
   170  func (s *fs) DeleteMetadata(dgst digest.Digest, key string) error {
   171  	s.Lock()
   172  	defer s.Unlock()
   173  
   174  	return os.RemoveAll(filepath.Join(s.metadataDir(dgst), key))
   175  }