penguin/monok8s

k8s image for Mono Gateway Dev Kit

clitools/pkg/node/mounts.go

raw ยท 4101 bytes

package node

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
	system "example.com/monok8s/pkg/system"
	"k8s.io/klog/v2"
)

const (
	altRootMount     = "/run/altrootfs"
	altImageStoreSrc = "/run/altrootfs/usr/lib/monok8s/imagestore"
)

func MountAltImageStore(ctx context.Context, nctx *NodeContext) error {
	// Make mountpoints first.
	if err := os.MkdirAll(altRootMount, 0o755); err != nil {
		return fmt.Errorf("mkdir %s: %w", altRootMount, err)
	}

	altDev := monov1alpha1.AltPartDeviceLink
	if mounted, err := isMounted(altRootMount); err != nil {
		return fmt.Errorf("check mount %s: %w", altRootMount, err)
	} else if !mounted {
		klog.InfoS("mounting alt rootfs", "device", altDev, "target", altRootMount)
		if _, err := nctx.SystemRunner.RunWithOptions(
			ctx,
			"mount",
			[]string{"-o", "ro", altDev, altRootMount},
			system.RunOptions{Timeout: 30 * time.Second},
		); err != nil {
			klog.Errorf("mount alt rootfs %s on %s: %w", altDev, altRootMount, err)
		}
	}

	// If the alt imagestore doesn't exist, don't fail hard unless you want strict behavior.
	st, err := os.Stat(altImageStoreSrc)
	if err != nil {

		// Unmount immediately
		_ = safeUnmount(ctx, nctx, altRootMount)

		if os.IsNotExist(err) {
			klog.InfoS(
				"alt imagestore not found; proceeding without it",
				"path", altImageStoreSrc,
			)
			err = writeCRIOStorageConfig(ctx, nctx, "")
			if err != nil {
				return err
			}
			return nil
		}

		return fmt.Errorf("stat alt imagestore %s: %w", altImageStoreSrc, err)
	}

	if !st.IsDir() {
		return fmt.Errorf("alt imagestore exists but is not a directory: %s", altImageStoreSrc)
	}

	err = writeCRIOStorageConfig(ctx, nctx, altImageStoreSrc)
	if err != nil {
		return err
	}

	return nil
}

func UnmountAltImageStore(ctx context.Context, nctx *NodeContext) error {

	mounted, err := isMounted(altRootMount)
	if err != nil {
		return err
	}

	if mounted {
		if err := writeCRIOStorageConfig(ctx, nctx, ""); err != nil {
			return err
		}
		err := RestartCRIO(ctx, nctx)
		if err != nil {
			return err
		}
	}

	var errs []string

	if err := safeUnmount(ctx, nctx, altRootMount); err != nil {
		errs = append(errs, err.Error())
	}

	if len(errs) > 0 {
		return fmt.Errorf(strings.Join(errs, "; "))
	}
	return nil
}

func isMounted(path string) (bool, error) {
	f, err := os.Open("/proc/self/mountinfo")
	if err != nil {
		return false, fmt.Errorf("open /proc/self/mountinfo: %w", err)
	}
	defer f.Close()

	target := filepath.Clean(path)

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		fields := strings.Fields(line)
		if len(fields) < 5 {
			continue
		}

		mountPoint, err := unescapeMountInfo(fields[4])
		if err != nil {
			return false, fmt.Errorf("decode mountpoint %q: %w", fields[4], err)
		}

		if filepath.Clean(mountPoint) == target {
			return true, nil
		}
	}

	if err := scanner.Err(); err != nil {
		return false, fmt.Errorf("scan /proc/self/mountinfo: %w", err)
	}

	return false, nil
}

func safeUnmount(ctx context.Context, nctx *NodeContext, path string) error {
	mounted, err := isMounted(path)
	if err != nil {
		return fmt.Errorf("check mountpoint %s: %w", path, err)
	}
	if !mounted {
		return nil
	}

	klog.InfoS("unmounting", "target", path)
	_, err = nctx.SystemRunner.RunWithOptions(
		ctx,
		"umount",
		[]string{path},
		system.RunOptions{Timeout: 30 * time.Second},
	)
	if err != nil {
		return fmt.Errorf("umount %s: %w", path, err)
	}
	return nil
}

func unescapeMountInfo(s string) (string, error) {
	var b strings.Builder
	b.Grow(len(s))

	for i := 0; i < len(s); i++ {
		if s[i] != '\\' {
			b.WriteByte(s[i])
			continue
		}

		if i+3 >= len(s) {
			return "", fmt.Errorf("truncated escape sequence")
		}

		esc := s[i+1 : i+4]
		switch esc {
		case "040":
			b.WriteByte(' ')
		case "011":
			b.WriteByte('\t')
		case "012":
			b.WriteByte('\n')
		case "134":
			b.WriteByte('\\')
		default:
			return "", fmt.Errorf("unsupported escape sequence \\%s", esc)
		}

		i += 3
	}

	return b.String(), nil
}