Files
immucore/pkg/mount/mount.go

498 lines
15 KiB
Go
Raw Normal View History

2023-01-12 19:10:10 +01:00
package mount
import (
2023-02-01 18:01:58 +01:00
"context"
2023-01-12 19:10:10 +01:00
"fmt"
"os"
"path/filepath"
2023-02-03 12:37:12 +01:00
"strings"
2023-02-01 18:01:58 +01:00
"time"
2023-01-12 19:10:10 +01:00
"github.com/containerd/containerd/mount"
"github.com/deniswernert/go-fstab"
2023-02-03 12:37:12 +01:00
"github.com/hashicorp/go-multierror"
"github.com/joho/godotenv"
2023-02-06 15:41:52 +01:00
internalUtils "github.com/kairos-io/immucore/internal/utils"
2023-02-01 18:01:58 +01:00
"github.com/kairos-io/kairos/pkg/utils"
"github.com/kairos-io/kairos/sdk/state"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/sanity-io/litter"
2023-02-01 18:01:58 +01:00
"github.com/spectrocloud-labs/herd"
2023-01-12 19:10:10 +01:00
)
2023-02-01 18:01:58 +01:00
type State struct {
Logger zerolog.Logger
2023-02-03 12:37:12 +01:00
Rootdir string // e.g. /sysroot inside initrd with pivot, / with nopivot
TargetImage string // e.g. /cOS/active.img
TargetLabel string // e.g. COS_ACTIVE
// /run/cos-layout.env (different!)
2023-02-02 14:00:44 +01:00
OverlayDir []string // e.g. /var
BindMounts []string // e.g. /etc/kubernetes
2023-02-01 19:06:51 +01:00
CustomMounts map[string]string // e.g. diskid : mountpoint
2023-02-03 12:37:12 +01:00
StateDir string // e.g. "/usr/local/.state"
MountRoot bool // e.g. if true, it tries to find the image to loopback mount
2023-02-01 19:27:01 +01:00
2023-02-03 12:37:12 +01:00
fstabs []*fstab.Mount
2023-02-01 19:27:01 +01:00
}
const (
2023-02-03 12:37:12 +01:00
opCustomMounts = "custom-mount"
opDiscoverState = "discover-state"
opMountState = "mount-state"
opMountBind = "mount-bind"
2023-02-02 14:00:44 +01:00
opMountRoot = "mount-root"
opOverlayMount = "overlay-mount"
opWriteFstab = "write-fstab"
opMountBaseOverlay = "mount-base-overlay"
2023-02-02 15:12:33 +01:00
opMountOEM = "mount-oem"
2023-02-03 12:37:12 +01:00
opRootfsHook = "rootfs-hook"
opLoadConfig = "load-config"
2023-02-01 19:27:01 +01:00
)
2023-02-02 14:00:44 +01:00
func (s *State) path(p ...string) string {
return filepath.Join(append([]string{s.Rootdir}, p...)...)
}
func (s *State) WriteFstab(fstabFile string) func(context.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
2023-02-02 14:00:44 +01:00
return func(ctx context.Context) error {
for _, fst := range s.fstabs {
select {
case <-ctx.Done():
default:
log.Logger.Debug().Str("fstabline", litter.Sdump(fst)).Str("fstabfile", fstabFile).Msg("Writing fstab line")
2023-02-02 14:00:44 +01:00
f, err := os.OpenFile(fstabFile,
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
// As we mount on /sysroot during initramfs but the fstab file is for the real init process, we need to remove
// Any mentions to /sysroot from the fstab lines, otherwise they wont work
fstCleaned := strings.ReplaceAll(fst.String(), "/sysroot", "")
toWrite := fmt.Sprintf("%s\n", fstCleaned)
if _, err := f.WriteString(toWrite); err != nil {
2023-02-02 14:00:44 +01:00
return err
}
log.Logger.Debug().Str("fstabline", fstCleaned).Str("fstabfile", fstabFile).Msg("Done fstab line")
2023-02-02 14:00:44 +01:00
}
}
return nil
}
}
2023-02-03 12:37:12 +01:00
func (s *State) RunStageOp(stage string) func(context.Context) error {
return func(ctx context.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
if stage == "rootfs" {
if _, err := os.Stat("/system"); os.IsNotExist(err) {
s.Logger.Debug().Str("from", "/sysroot/system").Str("to", "/system").Msg("Creating symlink")
err = os.Symlink("/sysroot/system", "/system")
if err != nil {
s.Logger.Err(err).Msg("creating symlink")
return err
}
}
}
cmd := fmt.Sprintf("elemental run-stage %s", stage)
log.Logger.Debug().Str("cmd", cmd).Msg("")
output, err := utils.SH(cmd)
log.Logger.Debug().Str("output", output).Msg("")
2023-02-03 12:37:12 +01:00
return err
}
}
2023-02-02 14:00:44 +01:00
func (s *State) MountOP(what, where, t string, options []string, timeout time.Duration) func(context.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
2023-02-02 14:00:44 +01:00
return func(c context.Context) error {
cc := time.After(timeout)
2023-02-02 14:00:44 +01:00
for {
select {
default:
if _, err := os.Stat(where); os.IsNotExist(err) {
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Msg("Mount point does not exist, creating")
err = os.MkdirAll(where, os.ModeDir|os.ModePerm)
if err != nil {
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(err).Msg("Creating dir")
continue
}
}
2023-02-02 14:00:44 +01:00
time.Sleep(1 * time.Second)
mountPoint := mount.Mount{
Type: t,
Source: what,
Options: options,
}
tmpFstab := mountToStab(mountPoint)
tmpFstab.File = where
2023-02-02 14:00:44 +01:00
op := mountOperation{
MountOption: mountPoint,
FstabEntry: *tmpFstab,
2023-02-02 14:00:44 +01:00
Target: where,
}
err := op.run()
if err != nil {
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(err).Msg("mounting")
2023-02-02 14:00:44 +01:00
continue
}
s.fstabs = append(s.fstabs, tmpFstab)
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Msg("Mounted")
2023-02-02 14:00:44 +01:00
return nil
case <-c.Done():
e := fmt.Errorf("context canceled")
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(e).Msg("mount canceled")
return e
case <-cc:
e := fmt.Errorf("timeout exhausted")
log.Logger.Debug().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Err(e).Msg("Mount timeout")
return e
2023-02-02 14:00:44 +01:00
}
}
}
}
2023-02-02 15:12:33 +01:00
func (s *State) WriteDAG(g *herd.Graph) (out string) {
for i, layer := range g.Analyze() {
out += fmt.Sprintf("%d.\n", (i + 1))
for _, op := range layer {
if op.Error != nil {
out += fmt.Sprintf(" <%s> (error: %s) (background: %t) (weak: %t)\n", op.Name, op.Error.Error(), op.Background, op.WeakDeps)
} else {
out += fmt.Sprintf(" <%s> (background: %t) (weak: %t)\n", op.Name, op.Background, op.WeakDeps)
}
}
}
return
}
2023-02-03 12:37:12 +01:00
func readEnv(file string) (map[string]string, error) {
var envMap map[string]string
var err error
f, err := os.Open(file)
if err != nil {
return envMap, err
}
defer f.Close()
envMap, err = godotenv.Parse(f)
if err != nil {
return envMap, err
}
return envMap, err
}
2023-02-01 18:01:58 +01:00
func (s *State) Register(g *herd.Graph) error {
var err error
2023-02-01 18:01:58 +01:00
runtime, err := state.NewRuntime()
if err != nil {
s.Logger.Debug().Err(err).Msg("")
return err
}
s.Logger.Debug().Str("state", litter.Sdump(runtime)).Msg("Register")
2023-02-03 12:37:12 +01:00
// TODO: add hooks, fstab (might have missed some), systemd compat
// TODO: We should also set tmpfs here (not -related)
2023-02-01 19:27:01 +01:00
2023-02-03 12:37:12 +01:00
// All of this below need to run after rootfs stage runs (so the layout file is created)
2023-02-01 22:51:31 +01:00
// This is legacy - in UKI we don't need to found the img, this needs to run in a conditional
if s.MountRoot {
2023-02-03 12:37:12 +01:00
// setup loopback mount for the image target for booting
s.Logger.Debug().Str("what", opDiscoverState).Msg("Add operation")
err = g.Add(opDiscoverState,
2023-02-01 22:51:31 +01:00
herd.WithDeps(opMountState),
herd.WithCallback(
func(ctx context.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
cmd := fmt.Sprintf("losetup --show -f %s", s.path("/run/initramfs/cos-state", s.TargetImage))
log.Logger.Debug().Str("targetImage", s.TargetImage).Str("path", s.Rootdir).Str("fullcmd", cmd).Msg("Mounting image")
_, err := utils.SH(cmd)
if err != nil {
log.Logger.Debug().Err(err).Msg("")
}
2023-02-01 22:51:31 +01:00
return err
},
))
if err != nil {
s.Logger.Err(err)
}
2023-02-01 18:01:58 +01:00
2023-02-03 12:37:12 +01:00
// mount the state partition so to find the loopback device
// Itxaka: what if its recovery?
s.Logger.Debug().Str("what", opMountState).Msg("Add operation")
err = g.Add(opMountState,
2023-02-01 22:51:31 +01:00
herd.WithCallback(
s.MountOP(
runtime.State.Name,
2023-02-01 22:51:31 +01:00
s.path("/run/initramfs/cos-state"),
runtime.State.Type,
2023-02-01 22:51:31 +01:00
[]string{
"ro", // or rw
}, 60*time.Second),
),
)
if err != nil {
s.Logger.Err(err)
}
2023-02-01 22:51:31 +01:00
2023-02-03 12:37:12 +01:00
// mount the loopback device as root of the fs
s.Logger.Debug().Str("what", opMountRoot).Msg("Add operation")
err = g.Add(opMountRoot,
2023-02-01 22:51:31 +01:00
herd.WithDeps(opDiscoverState),
herd.WithCallback(
s.MountOP(
fmt.Sprintf("/dev/disk/by-label/%s", s.TargetLabel),
s.Rootdir,
"ext4", // are images always ext2?
2023-02-01 22:51:31 +01:00
[]string{
"ro", // or rw
"suid",
"dev",
"exec",
// "auto",
//"nouser",
2023-02-01 22:51:31 +01:00
"async",
}, 10*time.Second),
2023-02-01 22:51:31 +01:00
),
)
if err != nil {
s.Logger.Err(err)
}
2023-02-01 22:51:31 +01:00
}
2023-02-02 15:12:33 +01:00
2023-02-03 12:37:12 +01:00
// depending on /run/cos-layout.env
// This is building the mountRoot dependendency if it was enabled
2023-02-02 15:12:33 +01:00
mountRootCondition := herd.ConditionalOption(func() bool { return s.MountRoot }, herd.WithDeps(opMountRoot))
s.Logger.Debug().Bool("mountRootCondition", s.MountRoot).Msg("condition")
2023-02-02 15:12:33 +01:00
// TODO: this needs to be run after sysroot so we can link to /sysroot/system/oem and after /oem mounted
s.Logger.Debug().Str("what", opRootfsHook).Msg("Add operation")
err = g.Add(opRootfsHook, mountRootCondition, herd.WithDeps(opMountRoot, opMountOEM), herd.WithCallback(s.RunStageOp("rootfs")))
if err != nil {
s.Logger.Err(err).Msg("running rootfs stage")
}
2023-02-03 12:37:12 +01:00
// /run/cos/cos-layout.env
2023-02-03 12:37:12 +01:00
// populate state bindmounts, overlaymounts, custommounts
s.Logger.Debug().Str("what", opLoadConfig).Msg("Add operation")
err = g.Add(opLoadConfig,
2023-02-03 12:37:12 +01:00
herd.WithDeps(opRootfsHook),
herd.WithCallback(func(ctx context.Context) error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
if s.CustomMounts == nil {
s.CustomMounts = map[string]string{}
}
env, err := readEnv("/run/cos/cos-layout.env")
2023-02-03 12:37:12 +01:00
if err != nil {
log.Logger.Err(err).Msg("Reading env")
2023-02-03 12:37:12 +01:00
return err
}
log.Logger.Debug().Str("envfile", litter.Sdump(env)).Msg("loading cos layout")
2023-02-03 12:37:12 +01:00
// populate from env here
s.OverlayDir = strings.Split(env["RW_PATHS"], " ")
// TODO: PERSISTENT_STATE_TARGET /usr/local/.state
s.BindMounts = strings.Split(env["PERSISTENT_STATE_PATHS"], " ")
log.Logger.Debug().Strs("paths", s.BindMounts).Msg("persistent paths")
log.Logger.Debug().Str("pathsraw", env["PERSISTENT_STATE_PATHS"]).Msg("persistent paths")
2023-02-03 12:37:12 +01:00
2023-02-06 15:41:52 +01:00
s.StateDir = env["PERSISTENT_STATE_TARGET"]
if s.StateDir == "" {
s.StateDir = "/usr/local/.state"
}
// s.CustomMounts is special:
// It gets parsed by the cmdline (TODO)
// and from the env var
// https://github.com/kairos-io/packages/blob/7c3581a8ba6371e5ce10c3a98bae54fde6a505af/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-generator.sh#L71
// https://github.com/kairos-io/packages/blob/7c3581a8ba6371e5ce10c3a98bae54fde6a505af/packages/system/dracut/immutable-rootfs/30cos-immutable-rootfs/cos-mount-layout.sh#L80
2023-02-06 16:02:18 +01:00
addLine := func(d string) {
dat := strings.Split(d, ":")
2023-02-06 15:41:52 +01:00
if len(dat) == 2 {
disk := dat[0]
path := dat[1]
s.CustomMounts[disk] = path
}
}
2023-02-06 16:02:18 +01:00
for _, v := range append(internalUtils.ReadCMDLineArg("rd.cos.mount="), strings.Split(env["VOLUMES"], " ")...) {
addLine(internalUtils.ParseMount(v))
}
2023-02-06 15:41:52 +01:00
2023-02-03 12:37:12 +01:00
return nil
}))
if err != nil {
s.Logger.Err(err)
}
2023-02-01 22:51:31 +01:00
// end sysroot mount
2023-02-01 18:01:58 +01:00
2023-02-01 22:33:44 +01:00
// overlay mount start
if rootFSType(s.Rootdir) != "overlay" {
s.Logger.Debug().Str("what", opMountBaseOverlay).Msg("Add operation")
err = g.Add(opMountBaseOverlay,
2023-02-01 22:33:44 +01:00
herd.WithCallback(
func(ctx context.Context) error {
2023-02-06 15:28:22 +01:00
op, err := baseOverlay(Overlay{
2023-02-01 22:33:44 +01:00
Base: "/run/overlay",
BackingBase: "tmpfs:20%",
})
if err != nil {
return err
}
s.fstabs = append(s.fstabs, &op.FstabEntry)
return op.run()
},
),
)
if err != nil {
s.Logger.Err(err)
}
2023-02-01 22:33:44 +01:00
}
2023-02-02 14:00:44 +01:00
overlayCondition := herd.ConditionalOption(func() bool { return rootFSType(s.Rootdir) != "overlay" }, herd.WithDeps(opMountBaseOverlay))
s.Logger.Debug().Bool("overlaycondition", rootFSType(s.Rootdir) != "overlay").Msg("condition")
2023-02-01 19:06:51 +01:00
// TODO: Add fsck
// mount overlay
s.Logger.Debug().Str("what", opOverlayMount).Msg("Add operation")
err = g.Add(
2023-02-03 12:37:12 +01:00
opOverlayMount,
overlayCondition,
herd.WithDeps(opLoadConfig),
mountRootCondition,
herd.WithCallback(
func(ctx context.Context) error {
var err error
for _, p := range s.OverlayDir {
2023-02-01 19:06:51 +01:00
op, err := mountWithBaseOverlay(p, s.Rootdir, "/run/overlay")
if err != nil {
return err
}
s.fstabs = append(s.fstabs, &op.FstabEntry)
2023-02-03 12:37:12 +01:00
err = multierror.Append(err, op.run())
}
2023-02-01 19:06:51 +01:00
2023-02-03 12:37:12 +01:00
return err
},
),
)
if err != nil {
s.Logger.Err(err)
}
s.Logger.Debug().Str("what", opCustomMounts).Msg("Add operation")
err = g.Add(
2023-02-03 12:37:12 +01:00
opCustomMounts,
mountRootCondition,
overlayCondition,
herd.WithDeps(opLoadConfig),
herd.WithCallback(func(ctx context.Context) error {
s.Logger.Debug().Msg("Start" + opCustomMounts)
2023-02-03 12:37:12 +01:00
var err error
for what, where := range s.CustomMounts {
// TODO: scan for the custom mount disk to know the underlying fs and set it proper
fstype := "auto"
// Translate label to disk for COS_PERSISTENT
if strings.Contains(what, "COS_PERSISTENT") {
fstype = runtime.Persistent.Type
}
s.Logger.Debug().Str("what", what).Str("where", s.path(where)).Str("type", fstype).Msg("mounting custom mounts")
2023-02-03 12:37:12 +01:00
err = multierror.Append(err, s.MountOP(
what,
s.path(where),
fstype,
2023-02-01 19:06:51 +01:00
[]string{
"ro", // or rw
2023-02-03 12:37:12 +01:00
},
10*time.Second,
2023-02-03 12:37:12 +01:00
)(ctx))
}
s.Logger.Debug().Msg("End" + opCustomMounts)
2023-02-03 12:37:12 +01:00
return err
}),
)
if err != nil {
s.Logger.Err(err)
}
2023-02-01 19:06:51 +01:00
2023-02-01 19:27:01 +01:00
// mount state is defined over a custom mount (/usr/local/.state for instance, needs to be mounted over a device)
s.Logger.Debug().Str("what", opMountBind).Msg("Add operation")
err = g.Add(
2023-02-03 12:37:12 +01:00
opMountBind,
overlayCondition,
mountRootCondition,
herd.WithDeps(opCustomMounts, opLoadConfig),
herd.WithCallback(
func(ctx context.Context) error {
var err error
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).With().Logger()
log.Logger.Debug().Msg("Start" + opMountBind)
log.Logger.Debug().Msg("Mounting bind")
log.Logger.Debug().Strs("binds", s.BindMounts).Msg("Mounting bind")
2023-02-03 12:37:12 +01:00
for _, p := range s.BindMounts {
log.Logger.Debug().Str("what", p).Str("where", s.StateDir).Msg("Mounting bind")
op := mountBind(p, s.Rootdir, s.StateDir)
2023-02-01 19:06:51 +01:00
s.fstabs = append(s.fstabs, &op.FstabEntry)
2023-02-03 12:37:12 +01:00
err = multierror.Append(err, op.run())
}
log.Logger.Debug().Msg("End" + opMountBind)
log.Logger.Err(err).Send()
2023-02-03 12:37:12 +01:00
return err
},
),
)
if err != nil {
s.Logger.Err(err)
}
2023-02-01 22:33:44 +01:00
// overlay mount end
s.Logger.Debug().Str("what", opMountOEM).Msg("Add operation")
err = g.Add(opMountOEM,
2023-02-02 15:12:33 +01:00
overlayCondition,
mountRootCondition,
2023-02-01 18:01:58 +01:00
herd.WithCallback(
s.MountOP(
runtime.OEM.Name,
2023-02-01 22:33:44 +01:00
s.path("/oem"),
runtime.OEM.Type,
2023-02-01 18:01:58 +01:00
[]string{
"rw",
"suid",
"dev",
"exec",
//"noauto",
//"nouser",
2023-02-01 18:01:58 +01:00
"async",
}, 10*time.Second),
2023-02-01 18:01:58 +01:00
),
)
if err != nil {
s.Logger.Err(err)
}
s.Logger.Debug().Str("what", opWriteFstab).Msg("Add operation")
err = g.Add(opWriteFstab,
2023-02-02 14:00:44 +01:00
overlayCondition,
2023-02-03 12:37:12 +01:00
mountRootCondition,
herd.WithDeps(opMountOEM, opCustomMounts, opMountBind, opOverlayMount),
2023-02-02 15:12:33 +01:00
herd.WeakDeps,
herd.WithCallback(s.WriteFstab(s.path("/etc/fstab"))))
if err != nil {
s.Logger.Err(err)
}
return err
2023-02-01 18:01:58 +01:00
}