package snapshot
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
"github.com/containerd/containerd/snapshots"
"github.com/opencontainers/go-digest"
"github.com/openeuler/Conch/internal/snapshot/common"
)
const chainIDPrefix = "sha256:"
func CalculateSnapshotID(namespace, key, parent string) (string, error) {
if parent == "" {
dgst := digest.FromString(fmt.Sprintf("%s/%s", namespace, key))
return dgst.String(), nil
}
diffID := digest.FromString(fmt.Sprintf("%s/%s", namespace, key))
chainID := calculateChainID(parent, diffID.String())
return chainID, nil
}
func calculateChainID(parentChainID, diffID string) string {
data := parentChainID + " " + diffID
hash := sha256.Sum256([]byte(data))
return chainIDPrefix + hex.EncodeToString(hash[:])
}
func prepareSnapshotFiles(conf *SnapshotConfig) error {
return os.MkdirAll(conf.SnapDir(), common.DirMode)
}
func listRootfsLayerErofs(rootfsMount string) ([]string, error) {
entries, err := os.ReadDir(rootfsMount)
if err != nil {
return nil, fmt.Errorf("read dir %s: %w", rootfsMount, err)
}
const (
layerPrefix = "layer"
layerSuffix = ".erofs"
)
type layerEntry struct {
name string
index int
}
layers := make([]layerEntry, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasPrefix(name, layerPrefix) || !strings.HasSuffix(name, layerSuffix) {
continue
}
numPart := strings.TrimPrefix(name, layerPrefix)
numPart = strings.TrimSuffix(numPart, layerSuffix)
idx, err := strconv.Atoi(numPart)
if err != nil {
continue
}
if idx < 0 {
continue
}
layers = append(layers, layerEntry{name: name, index: idx})
}
if len(layers) == 0 {
return []string{}, nil
}
sort.Slice(layers, func(i, j int) bool {
return layers[i].index < layers[j].index
})
result := make([]string, len(layers))
for i, layer := range layers {
result[i] = layer.name
}
return result, nil
}
func prepareSparseMemfile(conf *SnapshotConfig, targetDir string) error {
memFile := filepath.Join(targetDir, common.MemFileName)
if err := os.MkdirAll(filepath.Dir(memFile), common.DirMode); err != nil {
return err
}
f, err := os.OpenFile(memFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, common.FileMode)
if err != nil {
return fmt.Errorf("open memfile: %w", err)
}
defer f.Close()
if err := f.Truncate(conf.MemSize * common.MemMB); err != nil {
return fmt.Errorf("truncate memfile: %w", err)
}
return nil
}
func ensureMemFile(conf *SnapshotConfig, memDir string, createIfMissing bool) error {
memFile := filepath.Join(memDir, common.MemFileName)
if _, err := os.Stat(memFile); err == nil {
return nil
}
if !createIfMissing {
return fmt.Errorf("mem.img not found at %s", memFile)
}
return prepareSparseMemfile(conf, memDir)
}
func getSnapshotBasePath(workDir, namespace string) string {
return filepath.Join(workDir, "snapshot", namespace)
}
func snapshotPathName(snapshotID string) string {
return strings.ReplaceAll(snapshotID, ":", "")
}
func getActiveMountPath(workDir, namespace, sandboxID, mountKind string) string {
return filepath.Join(getSnapshotBasePath(workDir, namespace), sandboxID, mountKind)
}
func getSharedMountPath(workDir, namespace, snapshotID string) string {
return filepath.Join(getSnapshotBasePath(workDir, namespace), common.SnapshotSharedDir, snapshotPathName(snapshotID))
}
func getMemKeyFromRootfs(rootfsKey string) string {
return rootfsKey + common.MemKeySuffix
}
func getRootfsViewAliasKey(sandboxID string) string {
return fmt.Sprintf("view-%s-%s", common.SnapshotMountRootfs, sandboxID)
}
func getMemViewAliasKey(sandboxID string) string {
return fmt.Sprintf("view-%s-%s", common.SnapshotMountMem, sandboxID)
}
func getVMViewAliasKey(sandboxID string) string {
return fmt.Sprintf("view-%s-%s", common.SnapshotMountVM, sandboxID)
}
func getSharedViewSnapshotKey(mountKind, snapshotID string) string {
return fmt.Sprintf("shared-%s-%s", mountKind, snapshotPathName(snapshotID))
}
func cleanupEmptySnapshotParents(mountPoint string) error {
dir := filepath.Dir(mountPoint)
for {
base := filepath.Base(dir)
if base == "." || base == string(filepath.Separator) || base == "snapshot" {
return nil
}
err := os.Remove(dir)
if err == nil {
dir = filepath.Dir(dir)
continue
}
if os.IsNotExist(err) {
dir = filepath.Dir(dir)
continue
}
if errors.Is(err, syscall.ENOTEMPTY) || errors.Is(err, syscall.EEXIST) {
return nil
}
return err
}
}
func mergeLabels(info *snapshots.Info, conf *SnapshotConfig) {
for k, v := range info.Labels {
switch k {
case common.SnapshotLabelMemSize:
mSize, err := strconv.ParseInt(v, 10, 64)
if err == nil {
conf.MemSize = mSize
}
case common.SnapshotLabelRootfs:
conf.Rootfs = v
case common.SnapshotLabelSnapshotDir:
conf.RootDir = v
default:
conf.Labels[k] = v
}
}
}