mirror of
https://github.com/kairos-io/immucore.git
synced 2025-04-27 11:12:30 +00:00
290 lines
8.8 KiB
Go
290 lines
8.8 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/mount"
|
|
"github.com/deniswernert/go-fstab"
|
|
"github.com/kairos-io/immucore/internal/constants"
|
|
internalUtils "github.com/kairos-io/immucore/internal/utils"
|
|
"github.com/rs/zerolog"
|
|
"github.com/spectrocloud-labs/herd"
|
|
)
|
|
|
|
type State struct {
|
|
Rootdir string // where to mount the root partition e.g. /sysroot inside initrd with pivot, / with nopivot
|
|
TargetImage string // image from the state partition to mount as loop device e.g. /cOS/active.img
|
|
TargetDevice string // e.g. /dev/disk/by-label/COS_ACTIVE
|
|
RootMountMode string // How to mount the root partition e.g. ro or rw
|
|
|
|
// /run/cos-layout.env (different!)
|
|
OverlayDirs []string // e.g. /var
|
|
BindMounts []string // e.g. /etc/kubernetes
|
|
CustomMounts map[string]string // e.g. diskid : mountpoint
|
|
OverlayBase string // Overlay config, defaults to tmpfs:20%
|
|
StateDir string // e.g. "/usr/local/.state"
|
|
fstabs []*fstab.Mount
|
|
}
|
|
|
|
// SortedBindMounts returns the nodes with less depth first and in alphabetical order.
|
|
func (s *State) SortedBindMounts() []string {
|
|
bindMountsCopy := s.BindMounts
|
|
sort.Slice(bindMountsCopy, func(i, j int) bool {
|
|
iAry := strings.Split(bindMountsCopy[i], "/")
|
|
jAry := strings.Split(bindMountsCopy[j], "/")
|
|
iSize := len(iAry)
|
|
jSize := len(jAry)
|
|
if iSize == jSize {
|
|
return strings.Compare(iAry[len(iAry)-1], jAry[len(jAry)-1]) == -1
|
|
}
|
|
return iSize < jSize
|
|
})
|
|
return bindMountsCopy
|
|
}
|
|
|
|
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 {
|
|
return func(ctx context.Context) error {
|
|
// Create the file first, override if something is there, we don't care, we are on initramfs
|
|
f, err := os.Create(fstabFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Close()
|
|
for _, fst := range s.fstabs {
|
|
internalUtils.Log.Debug().Str("what", fst.String()).Msg("Adding line to fstab")
|
|
select {
|
|
case <-ctx.Done():
|
|
default:
|
|
f, err := os.OpenFile(fstabFile,
|
|
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := f.WriteString(fmt.Sprintf("%s\n", fst.String())); err != nil {
|
|
_ = f.Close()
|
|
return err
|
|
}
|
|
_ = f.Close()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// RunStageOp runs elemental run-stage stage. If its rootfs its special as it needs som symlinks
|
|
// If its uki we don't symlink as we already have everything in the sysroot.
|
|
func (s *State) RunStageOp(stage string) func(context.Context) error {
|
|
return func(_ context.Context) error {
|
|
switch stage {
|
|
case "rootfs":
|
|
if !internalUtils.IsUKI() {
|
|
if _, err := os.Stat("/system"); os.IsNotExist(err) {
|
|
err = os.Symlink("/sysroot/system", "/system")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("creating symlink")
|
|
}
|
|
}
|
|
if _, err := os.Stat("/oem"); os.IsNotExist(err) {
|
|
err = os.Symlink("/sysroot/oem", "/oem")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("creating symlink")
|
|
}
|
|
}
|
|
}
|
|
|
|
internalUtils.Log.Info().Msg("Running rootfs stage")
|
|
output, _ := internalUtils.RunStage("rootfs")
|
|
internalUtils.Log.Debug().Msg(output.String())
|
|
err := internalUtils.CreateIfNotExists(constants.LogDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e := os.WriteFile(filepath.Join(constants.LogDir, "rootfs_stage.log"), output.Bytes(), os.ModePerm)
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg("Writing log for rootfs stage")
|
|
}
|
|
return err
|
|
case "initramfs":
|
|
// Not sure if it will work under UKI where the s.Rootdir is the current root already
|
|
internalUtils.Log.Info().Msg("Running initramfs stage")
|
|
if internalUtils.IsUKI() {
|
|
output, _ := internalUtils.RunStage("initramfs")
|
|
internalUtils.Log.Debug().Msg(output.String())
|
|
err := internalUtils.CreateIfNotExists(constants.LogDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e := os.WriteFile(filepath.Join(constants.LogDir, "initramfs_stage.log"), output.Bytes(), os.ModePerm)
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg("Writing log for initramfs stage")
|
|
}
|
|
return err
|
|
} else {
|
|
chroot := internalUtils.NewChroot(s.Rootdir)
|
|
return chroot.RunCallback(func() error {
|
|
output, _ := internalUtils.RunStage("initramfs")
|
|
internalUtils.Log.Debug().Msg(output.String())
|
|
err := internalUtils.CreateIfNotExists(constants.LogDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
e := os.WriteFile(filepath.Join(constants.LogDir, "initramfs_stage.log"), output.Bytes(), os.ModePerm)
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg("Writing log for initramfs stage")
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
default:
|
|
return errors.New("no stage that we know off")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MountOP creates and executes a mount operation.
|
|
func (s *State) MountOP(what, where, t string, options []string, timeout time.Duration) func(context.Context) error {
|
|
|
|
l := internalUtils.Log.With().Str("what", what).Str("where", where).Str("type", t).Strs("options", options).Logger()
|
|
// Not sure why this defaults to debuglevel when creating a sublogger, so make sure we set it properly
|
|
debug := len(internalUtils.ReadCMDLineArg("rd.immucore.debug")) > 0
|
|
if debug {
|
|
l = l.Level(zerolog.DebugLevel)
|
|
}
|
|
|
|
return func(c context.Context) error {
|
|
cc := time.After(timeout)
|
|
for {
|
|
select {
|
|
default:
|
|
// check fs type just-in-time before running the OP
|
|
if t != "tmpfs" {
|
|
fsType := internalUtils.DiskFSType(what)
|
|
// If not empty and it does not match
|
|
if fsType != "" && t != fsType {
|
|
t = fsType
|
|
}
|
|
}
|
|
|
|
err := internalUtils.CreateIfNotExists(where)
|
|
if err != nil {
|
|
l.Err(err).Msg("Creating dir")
|
|
continue
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
mountPoint := mount.Mount{
|
|
Type: t,
|
|
Source: what,
|
|
Options: options,
|
|
}
|
|
tmpFstab := internalUtils.MountToFstab(mountPoint)
|
|
tmpFstab.File = internalUtils.CleanSysrootForFstab(where)
|
|
op := mountOperation{
|
|
MountOption: mountPoint,
|
|
FstabEntry: *tmpFstab,
|
|
Target: where,
|
|
PrepareCallback: func() error {
|
|
_ = internalUtils.Fsck(what)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
err = op.run()
|
|
|
|
// If no error on mounting or error is already mounted, as that affects the sysroot
|
|
// for some reason it reports that its already mounted (systemd is mounting it behind our back!).
|
|
if err == nil || err != nil && errors.Is(err, constants.ErrAlreadyMounted) {
|
|
s.AddToFstab(tmpFstab)
|
|
} else {
|
|
l.Debug().Err(err).Msg("Mount not added to fstab")
|
|
}
|
|
|
|
// only continue the loop if it's an error and not an already mounted error
|
|
if err != nil && !errors.Is(err, constants.ErrAlreadyMounted) {
|
|
l.Warn().Err(err).Send()
|
|
continue
|
|
}
|
|
l.Info().Msg("mount done")
|
|
return nil
|
|
case <-c.Done():
|
|
e := fmt.Errorf("context canceled")
|
|
l.Err(e).Msg("mount canceled")
|
|
return e
|
|
case <-cc:
|
|
e := fmt.Errorf("timeout exhausted")
|
|
l.Err(e).Msg("Mount timeout")
|
|
return e
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WriteDAG writes the dag.
|
|
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) (run: %t)\n", op.Name, op.Error.Error(), op.Background, op.WeakDeps, op.Executed)
|
|
} else {
|
|
out += fmt.Sprintf(" <%s> (background: %t) (weak: %t) (run: %t)\n", op.Name, op.Background, op.WeakDeps, op.Executed)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// LogIfError will log if there is an error with the given context as message
|
|
// Context can be empty.
|
|
func (s *State) LogIfError(e error, msgContext string) {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
}
|
|
}
|
|
|
|
// LogIfErrorAndReturn will log if there is an error with the given context as message
|
|
// Context can be empty
|
|
// Will also return the error.
|
|
func (s *State) LogIfErrorAndReturn(e error, msgContext string) error {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
}
|
|
return e
|
|
}
|
|
|
|
// LogIfErrorAndPanic will log if there is an error with the given context as message
|
|
// Context can be empty
|
|
// Will also panic.
|
|
func (s *State) LogIfErrorAndPanic(e error, msgContext string) {
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Msg(msgContext)
|
|
internalUtils.Log.Fatal().Msg(e.Error())
|
|
}
|
|
}
|
|
|
|
// AddToFstab will try to add an entry to the fstab list
|
|
// Will check if the entry exists before adding it to avoid duplicates.
|
|
func (s *State) AddToFstab(tmpFstab *fstab.Mount) {
|
|
found := false
|
|
for _, f := range s.fstabs {
|
|
if f.Spec == tmpFstab.Spec {
|
|
internalUtils.Log.Debug().Interface("existing", f).Interface("duplicated", tmpFstab).Msg("Duplicated fstab entry found, not adding")
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
s.fstabs = append(s.fstabs, tmpFstab)
|
|
}
|
|
}
|