func (is *ImageStoreFS) PutBlobChunk(repo string, uuid string, from int64, to int64,
	body io.Reader) (int64, error) {
	if err := is.InitRepo(repo); err != nil {
		return -1, err
	}
	blobUploadPath := is.BlobUploadPath(repo, uuid)
	fi, err := os.Stat(blobUploadPath)
	if err != nil {
		return -1, errors.ErrUploadNotFound
	}
	if from != fi.Size() {
		is.log.Error().Int64("expected", from).Int64("actual", fi.Size()).
			Msg("invalid range start for blob upload")
		return -1, errors.ErrBadUploadRange
	}
	file, err := os.OpenFile(
		blobUploadPath,
		os.O_WRONLY|os.O_CREATE,
		0600,
	)
	if err != nil {
		is.log.Fatal().Err(err).Msg("failed to open file")
	}
	defer file.Close()
	if _, err := file.Seek(from, io.SeekStart); err != nil {
		is.log.Fatal().Err(err).Msg("failed to seek file")
	}
	n, err := io.Copy(file, body)
	return n, err
}
PutBlobChunk writes another chunk of data to the specified blob. It returns
the number of actual bytes to the blob.
package storage
import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"
	"github.com/anuvu/zot/errors"
	zlog "github.com/anuvu/zot/pkg/log"
	apexlog "github.com/apex/log"
	guuid "github.com/gofrs/uuid"
	godigest "github.com/opencontainers/go-digest"
	ispec "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/opencontainers/umoci"
	"github.com/opencontainers/umoci/oci/casext"
	"github.com/rs/zerolog"
)
const (
	BlobUploadDir = ".uploads"
	schemaVersion = 2
	gcDelay       = 1 * time.Hour
)
type BlobUpload struct {
	StoreName string
	ID        string
}
type StoreController struct {
	DefaultStore ImageStore
	SubStore     map[string]ImageStore
}
type ImageStoreFS struct {
	rootDir     string
	lock        *sync.RWMutex
	blobUploads map[string]BlobUpload
	cache       *Cache
	gc          bool
	dedupe      bool
	log         zerolog.Logger
}
func (is *ImageStoreFS) RootDir() string {
	return is.rootDir
}
func (is *ImageStoreFS) DirExists(d string) bool {
	fi, err := os.Stat(d)
	if err != nil && os.IsNotExist(err) {
		return false
	}
	if !fi.IsDir() {
		return false
	}
	return true
}
func getRoutePrefix(name string) string {
	names := strings.SplitN(name, "/", 2)
	if len(names) != 2 {  
		if len(names) == 1 {
			return "/"
		}
	}
	return fmt.Sprintf("/%s", names[0])
}
func (sc StoreController) GetImageStore(name string) ImageStore {
	if sc.SubStore != nil {
		prefixName := getRoutePrefix(name)
		imgStore, ok := sc.SubStore[prefixName]
		if !ok {
			imgStore = sc.DefaultStore
		}
		return imgStore
	}
	return sc.DefaultStore
}
func NewImageStore(rootDir string, gc bool, dedupe bool, log zlog.Logger) ImageStore {
	if _, err := os.Stat(rootDir); os.IsNotExist(err) {
		if err := os.MkdirAll(rootDir, 0700); err != nil {
			log.Error().Err(err).Str("rootDir", rootDir).Msg("unable to create root dir")
			return nil
		}
	}
	is := &ImageStoreFS{
		rootDir:     rootDir,
		lock:        &sync.RWMutex{},
		blobUploads: make(map[string]BlobUpload),
		gc:          gc,
		dedupe:      dedupe,
		log:         log.With().Caller().Logger(),
	}
	if dedupe {
		is.cache = NewCache(rootDir, "cache", log)
	}
	if gc {
		apexlog.SetLevel(apexlog.DebugLevel)
		apexlog.SetHandler(apexlog.HandlerFunc(func(entry *apexlog.Entry) error {
			e := log.Debug()
			for k, v := range entry.Fields {
				e = e.Interface(k, v)
			}
			e.Msg(entry.Message)
			return nil
		}))
	}
	return is
}
func (is *ImageStoreFS) RLock() {
	is.lock.RLock()
}
func (is *ImageStoreFS) RUnlock() {
	is.lock.RUnlock()
}
func (is *ImageStoreFS) Lock() {
	is.lock.Lock()
}
func (is *ImageStoreFS) Unlock() {
	is.lock.Unlock()
}
func (is *ImageStoreFS) initRepo(name string) error {
	repoDir := path.Join(is.rootDir, name)
	err := ensureDir(path.Join(repoDir, "blobs"), is.log)
	if err != nil {
		is.log.Error().Err(err).Msg("error creating blobs subdir")
		return err
	}
	err = ensureDir(path.Join(repoDir, BlobUploadDir), is.log)
	if err != nil {
		is.log.Error().Err(err).Msg("error creating blob upload subdir")
		return err
	}
	ilPath := path.Join(repoDir, ispec.ImageLayoutFile)
	if _, err := os.Stat(ilPath); err != nil {
		il := ispec.ImageLayout{Version: ispec.ImageLayoutVersion}
		buf, err := json.Marshal(il)
		if err != nil {
			is.log.Panic().Err(err).Msg("unable to marshal JSON")
		}
		if err := ioutil.WriteFile(ilPath, buf, 0644); err != nil {  
			is.log.Error().Err(err).Str("file", ilPath).Msg("unable to write file")
			return err
		}
	}
	indexPath := path.Join(repoDir, "index.json")
	if _, err := os.Stat(indexPath); err != nil {
		index := ispec.Index{}
		index.SchemaVersion = 2
		buf, err := json.Marshal(index)
		if err != nil {
			is.log.Panic().Err(err).Msg("unable to marshal JSON")
		}
		if err := ioutil.WriteFile(indexPath, buf, 0644); err != nil {  
			is.log.Error().Err(err).Str("file", indexPath).Msg("unable to write file")
			return err
		}
	}
	return nil
}
func (is *ImageStoreFS) InitRepo(name string) error {
	is.Lock()
	defer is.Unlock()
	return is.initRepo(name)
}
func (is *ImageStoreFS) ValidateRepo(name string) (bool, error) {
	dir := path.Join(is.rootDir, name)
	if !is.DirExists(dir) {
		return false, errors.ErrRepoNotFound
	}
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("unable to read directory")
		return false, errors.ErrRepoNotFound
	}
	if len(files) < 3 {
		return false, errors.ErrRepoBadVersion
	}
	found := map[string]bool{
		"blobs":               false,
		ispec.ImageLayoutFile: false,
		"index.json":          false,
	}
	for _, file := range files {
		if file.Name() == "blobs" && !file.IsDir() {
			return false, nil
		}
		found[file.Name()] = true
	}
	for k, v := range found {
		if !v && k != BlobUploadDir {
			return false, nil
		}
	}
	buf, err := ioutil.ReadFile(path.Join(dir, ispec.ImageLayoutFile))
	if err != nil {
		return false, err
	}
	var il ispec.ImageLayout
	if err := json.Unmarshal(buf, &il); err != nil {
		return false, err
	}
	if il.Version != ispec.ImageLayoutVersion {
		return false, errors.ErrRepoBadVersion
	}
	return true, nil
}
func (is *ImageStoreFS) GetRepositories() ([]string, error) {
	dir := is.rootDir
	is.RLock()
	defer is.RUnlock()
	_, err := ioutil.ReadDir(dir)
	if err != nil {
		is.log.Error().Err(err).Msg("failure walking storage root-dir")
		return nil, err
	}
	stores := make([]string, 0)
	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			return nil
		}
		rel, err := filepath.Rel(is.rootDir, path)
		if err != nil {
			return nil
		}
		if ok, err := is.ValidateRepo(rel); !ok || err != nil {
			return nil
		}
		stores = append(stores, rel)
		return nil
	})
	return stores, err
}
func (is *ImageStoreFS) GetImageTags(repo string) ([]string, error) {
	dir := path.Join(is.rootDir, repo)
	if !is.DirExists(dir) {
		return nil, errors.ErrRepoNotFound
	}
	is.RLock()
	defer is.RUnlock()
	buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
	if err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json")
		return nil, errors.ErrRepoNotFound
	}
	var index ispec.Index
	if err := json.Unmarshal(buf, &index); err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
		return nil, errors.ErrRepoNotFound
	}
	tags := make([]string, 0)
	for _, manifest := range index.Manifests {
		v, ok := manifest.Annotations[ispec.AnnotationRefName]
		if ok {
			tags = append(tags, v)
		}
	}
	return tags, nil
}
func (is *ImageStoreFS) GetImageManifest(repo string, reference string) ([]byte, string, string, error) {
	dir := path.Join(is.rootDir, repo)
	if !is.DirExists(dir) {
		return nil, "", "", errors.ErrRepoNotFound
	}
	is.RLock()
	defer is.RUnlock()
	buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
	if err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json")
		if os.IsNotExist(err) {
			return nil, "", "", errors.ErrRepoNotFound
		}
		return nil, "", "", err
	}
	var index ispec.Index
	if err := json.Unmarshal(buf, &index); err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
		return nil, "", "", err
	}
	found := false
	var digest godigest.Digest
	mediaType := ""
	for _, m := range index.Manifests {
		if reference == m.Digest.String() {
			digest = m.Digest
			mediaType = m.MediaType
			found = true
			break
		}
		v, ok := m.Annotations[ispec.AnnotationRefName]
		if ok && v == reference {
			digest = m.Digest
			mediaType = m.MediaType
			found = true
			break
		}
	}
	if !found {
		return nil, "", "", errors.ErrManifestNotFound
	}
	p := path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded())
	buf, err = ioutil.ReadFile(p)
	if err != nil {
		is.log.Error().Err(err).Str("blob", p).Msg("failed to read manifest")
		if os.IsNotExist(err) {
			return nil, "", "", errors.ErrManifestNotFound
		}
		return nil, "", "", err
	}
	var manifest ispec.Manifest
	if err := json.Unmarshal(buf, &manifest); err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
		return nil, "", "", err
	}
	return buf, digest.String(), mediaType, nil
}
func (is *ImageStoreFS) PutImageManifest(repo string, reference string, mediaType string,
	body []byte) (string, error) {
	if err := is.InitRepo(repo); err != nil {
		is.log.Debug().Err(err).Msg("init repo")
		return "", err
	}
	if mediaType != ispec.MediaTypeImageManifest {
		is.log.Debug().Interface("actual", mediaType).
			Interface("expected", ispec.MediaTypeImageManifest).Msg("bad manifest media type")
		return "", errors.ErrBadManifest
	}
	if len(body) == 0 {
		is.log.Debug().Int("len", len(body)).Msg("invalid body length")
		return "", errors.ErrBadManifest
	}
	var m ispec.Manifest
	if err := json.Unmarshal(body, &m); err != nil {
		is.log.Error().Err(err).Msg("unable to unmarshal JSON")
		return "", errors.ErrBadManifest
	}
	if m.SchemaVersion != schemaVersion {
		is.log.Error().Int("SchemaVersion", m.SchemaVersion).Msg("invalid manifest")
		return "", errors.ErrBadManifest
	}
	for _, l := range m.Layers {
		digest := l.Digest
		blobPath := is.BlobPath(repo, digest)
		is.log.Info().Str("blobPath", blobPath).Str("reference", reference).Msg("manifest layers")
		if _, err := os.Stat(blobPath); err != nil {
			is.log.Error().Err(err).Str("blobPath", blobPath).Msg("unable to find blob")
			return digest.String(), errors.ErrBlobNotFound
		}
	}
	mDigest := godigest.FromBytes(body)
	refIsDigest := false
	d, err := godigest.Parse(reference)
	if err == nil {
		if d.String() != mDigest.String() {
			is.log.Error().Str("actual", mDigest.String()).Str("expected", d.String()).
				Msg("manifest digest is not valid")
			return "", errors.ErrBadManifest
		}
		refIsDigest = true
	}
	is.Lock()
	defer is.Unlock()
	dir := path.Join(is.rootDir, repo)
	buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
	if err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json")
		return "", err
	}
	var index ispec.Index
	if err := json.Unmarshal(buf, &index); err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
		return "", errors.ErrRepoBadVersion
	}
	updateIndex := true
	desc := ispec.Descriptor{MediaType: mediaType, Size: int64(len(body)), Digest: mDigest,
		Platform: &ispec.Platform{Architecture: "amd64", OS: "linux"}}
	if !refIsDigest {
		desc.Annotations = map[string]string{ispec.AnnotationRefName: reference}
	}
	for i, m := range index.Manifests {
		if reference == m.Digest.String() {
			desc = m
			updateIndex = false
			break
		}
		v, ok := m.Annotations[ispec.AnnotationRefName]
		if ok && v == reference {
			if m.Digest.String() == mDigest.String() {
				desc = m
				updateIndex = false
				break
			}
			is.log.Info().
				Int64("old size", desc.Size).
				Int64("new size", int64(len(body))).
				Str("old digest", desc.Digest.String()).
				Str("new digest", mDigest.String()).
				Msg("updating existing tag with new manifest contents")
			desc = m
			desc.Size = int64(len(body))
			desc.Digest = mDigest
			index.Manifests = append(index.Manifests[:i], index.Manifests[i+1:]...)
			break
		}
	}
	if !updateIndex {
		return desc.Digest.String(), nil
	}
	dir = path.Join(is.rootDir, repo, "blobs", mDigest.Algorithm().String())
	_ = ensureDir(dir, is.log)
	file := path.Join(dir, mDigest.Encoded())
	if err := ioutil.WriteFile(file, body, 0600); err != nil {
		is.log.Error().Err(err).Str("file", file).Msg("unable to write")
		return "", err
	}
	index.Manifests = append(index.Manifests, desc)
	dir = path.Join(is.rootDir, repo)
	file = path.Join(dir, "index.json")
	buf, err = json.Marshal(index)
	if err != nil {
		is.log.Error().Err(err).Str("file", file).Msg("unable to marshal JSON")
		return "", err
	}
	if err := ioutil.WriteFile(file, buf, 0644); err != nil {  
		is.log.Error().Err(err).Str("file", file).Msg("unable to write")
		return "", err
	}
	if is.gc {
		oci, err := umoci.OpenLayout(dir)
		if err != nil {
			return "", err
		}
		defer oci.Close()
		if err := oci.GC(context.Background(), ifOlderThan(is, repo, gcDelay)); err != nil {
			return "", err
		}
	}
	return desc.Digest.String(), nil
}
func (is *ImageStoreFS) DeleteImageManifest(repo string, reference string) error {
	dir := path.Join(is.rootDir, repo)
	if !is.DirExists(dir) {
		return errors.ErrRepoNotFound
	}
	isTag := false
	digest, err := godigest.Parse(reference)
	if err != nil {
		is.log.Debug().Str("invalid digest: ", reference).Msg("storage: assuming tag")
		isTag = true
	}
	is.Lock()
	defer is.Unlock()
	buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
	if err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json")
		return err
	}
	var index ispec.Index
	if err := json.Unmarshal(buf, &index); err != nil {
		is.log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
		return err
	}
	found := false
	var m ispec.Descriptor
	outIndex := index
	outIndex.Manifests = []ispec.Descriptor{}
	for _, m = range index.Manifests {
		if isTag {
			tag, ok := m.Annotations[ispec.AnnotationRefName]
			if ok && tag == reference {
				is.log.Debug().Str("deleting tag", tag).Msg("")
				digest = m.Digest
				found = true
				continue
			}
		} else if reference == m.Digest.String() {
			is.log.Debug().Str("deleting reference", reference).Msg("")
			found = true
			continue
		}
		outIndex.Manifests = append(outIndex.Manifests, m)
	}
	if !found {
		return errors.ErrManifestNotFound
	}
	dir = path.Join(is.rootDir, repo)
	file := path.Join(dir, "index.json")
	buf, err = json.Marshal(outIndex)
	if err != nil {
		return err
	}
	if err := ioutil.WriteFile(file, buf, 0644); err != nil {  
		return err
	}
	if is.gc {
		oci, err := umoci.OpenLayout(dir)
		if err != nil {
			return err
		}
		defer oci.Close()
		if err := oci.GC(context.Background(), ifOlderThan(is, repo, gcDelay)); err != nil {
			return err
		}
	}
	toDelete := true
	for _, m = range outIndex.Manifests {
		if digest.String() == m.Digest.String() {
			toDelete = false
			break
		}
	}
	if toDelete {
		p := path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded())
		_ = os.Remove(p)
	}
	return nil
}
func (is *ImageStoreFS) BlobUploadPath(repo string, uuid string) string {
	dir := path.Join(is.rootDir, repo)
	blobUploadPath := path.Join(dir, BlobUploadDir, uuid)
	return blobUploadPath
}
func (is *ImageStoreFS) NewBlobUpload(repo string) (string, error) {
	if err := is.InitRepo(repo); err != nil {
		is.log.Error().Err(err).Msg("error initializing repo")
		return "", err
	}
	uuid, err := guuid.NewV4()
	if err != nil {
		return "", err
	}
	u := uuid.String()
	blobUploadPath := is.BlobUploadPath(repo, u)
	file, err := os.OpenFile(blobUploadPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
	if err != nil {
		return "", errors.ErrRepoNotFound
	}
	defer file.Close()
	return u, nil
}
func (is *ImageStoreFS) GetBlobUpload(repo string, uuid string) (int64, error) {
	blobUploadPath := is.BlobUploadPath(repo, uuid)
	fi, err := os.Stat(blobUploadPath)
	if err != nil {
		if os.IsNotExist(err) {
			return -1, errors.ErrUploadNotFound
		}
		return -1, err
	}
	return fi.Size(), nil
}
func (is *ImageStoreFS) PutBlobChunkStreamed(repo string, uuid string, body io.Reader) (int64, error) {
	if err := is.InitRepo(repo); err != nil {
		return -1, err
	}
	blobUploadPath := is.BlobUploadPath(repo, uuid)
	_, err := os.Stat(blobUploadPath)
	if err != nil {
		return -1, errors.ErrUploadNotFound
	}
	file, err := os.OpenFile(
		blobUploadPath,
		os.O_WRONLY|os.O_CREATE,
		0600,
	)
	if err != nil {
		is.log.Fatal().Err(err).Msg("failed to open file")
	}
	defer file.Close()
	if _, err := file.Seek(0, io.SeekEnd); err != nil {
		is.log.Fatal().Err(err).Msg("failed to seek file")
	}
	n, err := io.Copy(file, body)
	return n, err
}