layer/filestore.go
500e77ba
 package layer
 
 import (
 	"compress/gzip"
05bd0435
 	"encoding/json"
500e77ba
 	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"regexp"
 	"strconv"
caef48f4
 	"strings"
500e77ba
 
 	"github.com/Sirupsen/logrus"
05bd0435
 	"github.com/docker/distribution"
500e77ba
 	"github.com/docker/distribution/digest"
 	"github.com/docker/docker/pkg/ioutils"
 )
 
 var (
 	stringIDRegexp      = regexp.MustCompile(`^[a-f0-9]{64}(-init)?$`)
 	supportedAlgorithms = []digest.Algorithm{
 		digest.SHA256,
 		// digest.SHA384, // Currently not used
 		// digest.SHA512, // Currently not used
 	}
 )
 
 type fileMetadataStore struct {
 	root string
 }
 
 type fileMetadataTransaction struct {
 	store *fileMetadataStore
c37bd10f
 	ws    *ioutils.AtomicWriteSet
500e77ba
 }
 
 // NewFSMetadataStore returns an instance of a metadata store
 // which is backed by files on disk using the provided root
 // as the root of metadata files.
 func NewFSMetadataStore(root string) (MetadataStore, error) {
 	if err := os.MkdirAll(root, 0700); err != nil {
 		return nil, err
 	}
 	return &fileMetadataStore{
 		root: root,
 	}, nil
 }
 
 func (fms *fileMetadataStore) getLayerDirectory(layer ChainID) string {
 	dgst := digest.Digest(layer)
 	return filepath.Join(fms.root, string(dgst.Algorithm()), dgst.Hex())
 }
 
 func (fms *fileMetadataStore) getLayerFilename(layer ChainID, filename string) string {
 	return filepath.Join(fms.getLayerDirectory(layer), filename)
 }
 
 func (fms *fileMetadataStore) getMountDirectory(mount string) string {
 	return filepath.Join(fms.root, "mounts", mount)
 }
 
 func (fms *fileMetadataStore) getMountFilename(mount, filename string) string {
 	return filepath.Join(fms.getMountDirectory(mount), filename)
 }
 
 func (fms *fileMetadataStore) StartTransaction() (MetadataTransaction, error) {
 	tmpDir := filepath.Join(fms.root, "tmp")
 	if err := os.MkdirAll(tmpDir, 0755); err != nil {
 		return nil, err
 	}
c37bd10f
 	ws, err := ioutils.NewAtomicWriteSet(tmpDir)
500e77ba
 	if err != nil {
 		return nil, err
 	}
c37bd10f
 
500e77ba
 	return &fileMetadataTransaction{
 		store: fms,
c37bd10f
 		ws:    ws,
500e77ba
 	}, nil
 }
 
 func (fm *fileMetadataTransaction) SetSize(size int64) error {
 	content := fmt.Sprintf("%d", size)
c37bd10f
 	return fm.ws.WriteFile("size", []byte(content), 0644)
500e77ba
 }
 
 func (fm *fileMetadataTransaction) SetParent(parent ChainID) error {
c37bd10f
 	return fm.ws.WriteFile("parent", []byte(digest.Digest(parent).String()), 0644)
500e77ba
 }
 
 func (fm *fileMetadataTransaction) SetDiffID(diff DiffID) error {
c37bd10f
 	return fm.ws.WriteFile("diff", []byte(digest.Digest(diff).String()), 0644)
500e77ba
 }
 
 func (fm *fileMetadataTransaction) SetCacheID(cacheID string) error {
c37bd10f
 	return fm.ws.WriteFile("cache-id", []byte(cacheID), 0644)
500e77ba
 }
 
2c60430a
 func (fm *fileMetadataTransaction) SetDescriptor(ref distribution.Descriptor) error {
05bd0435
 	jsonRef, err := json.Marshal(ref)
 	if err != nil {
 		return err
 	}
c37bd10f
 	return fm.ws.WriteFile("descriptor.json", jsonRef, 0644)
05bd0435
 }
 
a8f88ef4
 func (fm *fileMetadataTransaction) TarSplitWriter(compressInput bool) (io.WriteCloser, error) {
c37bd10f
 	f, err := fm.ws.FileWriter("tar-split.json.gz", os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
500e77ba
 	if err != nil {
 		return nil, err
 	}
a8f88ef4
 	var wc io.WriteCloser
 	if compressInput {
 		wc = gzip.NewWriter(f)
 	} else {
 		wc = f
 	}
500e77ba
 
a8f88ef4
 	return ioutils.NewWriteCloserWrapper(wc, func() error {
 		wc.Close()
500e77ba
 		return f.Close()
 	}), nil
 }
 
 func (fm *fileMetadataTransaction) Commit(layer ChainID) error {
 	finalDir := fm.store.getLayerDirectory(layer)
 	if err := os.MkdirAll(filepath.Dir(finalDir), 0755); err != nil {
 		return err
 	}
c37bd10f
 
 	return fm.ws.Commit(finalDir)
500e77ba
 }
 
 func (fm *fileMetadataTransaction) Cancel() error {
c37bd10f
 	return fm.ws.Cancel()
500e77ba
 }
 
 func (fm *fileMetadataTransaction) String() string {
c37bd10f
 	return fm.ws.String()
500e77ba
 }
 
 func (fms *fileMetadataStore) GetSize(layer ChainID) (int64, error) {
 	content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "size"))
 	if err != nil {
 		return 0, err
 	}
 
 	size, err := strconv.ParseInt(string(content), 10, 64)
 	if err != nil {
 		return 0, err
 	}
 
 	return size, nil
 }
 
 func (fms *fileMetadataStore) GetParent(layer ChainID) (ChainID, error) {
 	content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "parent"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return "", nil
 		}
 		return "", err
 	}
 
caef48f4
 	dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
500e77ba
 	if err != nil {
 		return "", err
 	}
 
 	return ChainID(dgst), nil
 }
 
 func (fms *fileMetadataStore) GetDiffID(layer ChainID) (DiffID, error) {
 	content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "diff"))
 	if err != nil {
 		return "", err
 	}
 
caef48f4
 	dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
500e77ba
 	if err != nil {
 		return "", err
 	}
 
 	return DiffID(dgst), nil
 }
 
 func (fms *fileMetadataStore) GetCacheID(layer ChainID) (string, error) {
caef48f4
 	contentBytes, err := ioutil.ReadFile(fms.getLayerFilename(layer, "cache-id"))
500e77ba
 	if err != nil {
 		return "", err
 	}
caef48f4
 	content := strings.TrimSpace(string(contentBytes))
500e77ba
 
caef48f4
 	if !stringIDRegexp.MatchString(content) {
500e77ba
 		return "", errors.New("invalid cache id value")
 	}
 
caef48f4
 	return content, nil
500e77ba
 }
 
2c60430a
 func (fms *fileMetadataStore) GetDescriptor(layer ChainID) (distribution.Descriptor, error) {
05bd0435
 	content, err := ioutil.ReadFile(fms.getLayerFilename(layer, "descriptor.json"))
 	if err != nil {
 		if os.IsNotExist(err) {
2c60430a
 			// only return empty descriptor to represent what is stored
 			return distribution.Descriptor{}, nil
05bd0435
 		}
 		return distribution.Descriptor{}, err
 	}
 
 	var ref distribution.Descriptor
 	err = json.Unmarshal(content, &ref)
 	if err != nil {
 		return distribution.Descriptor{}, err
 	}
 	return ref, err
 }
 
500e77ba
 func (fms *fileMetadataStore) TarSplitReader(layer ChainID) (io.ReadCloser, error) {
 	fz, err := os.Open(fms.getLayerFilename(layer, "tar-split.json.gz"))
 	if err != nil {
 		return nil, err
 	}
 	f, err := gzip.NewReader(fz)
 	if err != nil {
 		return nil, err
 	}
 
 	return ioutils.NewReadCloserWrapper(f, func() error {
 		f.Close()
 		return fz.Close()
 	}), nil
 }
 
 func (fms *fileMetadataStore) SetMountID(mount string, mountID string) error {
 	if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
 		return err
 	}
 	return ioutil.WriteFile(fms.getMountFilename(mount, "mount-id"), []byte(mountID), 0644)
 }
 
 func (fms *fileMetadataStore) SetInitID(mount string, init string) error {
 	if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
 		return err
 	}
 	return ioutil.WriteFile(fms.getMountFilename(mount, "init-id"), []byte(init), 0644)
 }
 
 func (fms *fileMetadataStore) SetMountParent(mount string, parent ChainID) error {
 	if err := os.MkdirAll(fms.getMountDirectory(mount), 0755); err != nil {
 		return err
 	}
 	return ioutil.WriteFile(fms.getMountFilename(mount, "parent"), []byte(digest.Digest(parent).String()), 0644)
 }
 
 func (fms *fileMetadataStore) GetMountID(mount string) (string, error) {
caef48f4
 	contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "mount-id"))
500e77ba
 	if err != nil {
 		return "", err
 	}
caef48f4
 	content := strings.TrimSpace(string(contentBytes))
500e77ba
 
caef48f4
 	if !stringIDRegexp.MatchString(content) {
500e77ba
 		return "", errors.New("invalid mount id value")
 	}
 
caef48f4
 	return content, nil
500e77ba
 }
 
 func (fms *fileMetadataStore) GetInitID(mount string) (string, error) {
caef48f4
 	contentBytes, err := ioutil.ReadFile(fms.getMountFilename(mount, "init-id"))
500e77ba
 	if err != nil {
 		if os.IsNotExist(err) {
 			return "", nil
 		}
 		return "", err
 	}
caef48f4
 	content := strings.TrimSpace(string(contentBytes))
500e77ba
 
caef48f4
 	if !stringIDRegexp.MatchString(content) {
500e77ba
 		return "", errors.New("invalid init id value")
 	}
 
caef48f4
 	return content, nil
500e77ba
 }
 
 func (fms *fileMetadataStore) GetMountParent(mount string) (ChainID, error) {
 	content, err := ioutil.ReadFile(fms.getMountFilename(mount, "parent"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return "", nil
 		}
 		return "", err
 	}
 
caef48f4
 	dgst, err := digest.ParseDigest(strings.TrimSpace(string(content)))
500e77ba
 	if err != nil {
 		return "", err
 	}
 
 	return ChainID(dgst), nil
 }
 
 func (fms *fileMetadataStore) List() ([]ChainID, []string, error) {
 	var ids []ChainID
 	for _, algorithm := range supportedAlgorithms {
 		fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, string(algorithm)))
 		if err != nil {
 			if os.IsNotExist(err) {
 				continue
 			}
 			return nil, nil, err
 		}
 
 		for _, fi := range fileInfos {
 			if fi.IsDir() && fi.Name() != "mounts" {
 				dgst := digest.NewDigestFromHex(string(algorithm), fi.Name())
 				if err := dgst.Validate(); err != nil {
 					logrus.Debugf("Ignoring invalid digest %s:%s", algorithm, fi.Name())
 				} else {
 					ids = append(ids, ChainID(dgst))
 				}
 			}
 		}
 	}
 
 	fileInfos, err := ioutil.ReadDir(filepath.Join(fms.root, "mounts"))
 	if err != nil {
 		if os.IsNotExist(err) {
 			return ids, []string{}, nil
 		}
 		return nil, nil, err
 	}
 
 	var mounts []string
 	for _, fi := range fileInfos {
 		if fi.IsDir() {
 			mounts = append(mounts, fi.Name())
 		}
 	}
 
 	return ids, mounts, nil
 }
 
 func (fms *fileMetadataStore) Remove(layer ChainID) error {
 	return os.RemoveAll(fms.getLayerDirectory(layer))
 }
 
 func (fms *fileMetadataStore) RemoveMount(mount string) error {
 	return os.RemoveAll(fms.getMountDirectory(mount))
 }