diff --git a/go.mod b/go.mod index 9f8bd8e..4d67f48 100644 --- a/go.mod +++ b/go.mod @@ -114,6 +114,7 @@ require ( github.com/rivo/uniseg v0.4.4 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/samber/lo v1.37.0 // indirect + github.com/sanity-io/litter v1.5.5 // indirect github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect diff --git a/go.sum b/go.sum index 5a12480..6b35533 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -460,6 +461,7 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -500,6 +502,7 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= +github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -539,6 +542,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 89f17bd..8eba863 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -103,6 +103,7 @@ const ( UkiDefaultcdrom = "/dev/sr0" UkiDefaultcdromFsType = "iso9660" UkiDefaultEfiimgFsType = "vfat" + UkiSysrootDir = "sysroot" PersistentStateTarget = "/usr/local/.state" LogDir = "/run/immucore" ) diff --git a/pkg/mount/dag_steps.go b/pkg/mount/dag_steps.go index f20b220..f9f4e8d 100644 --- a/pkg/mount/dag_steps.go +++ b/pkg/mount/dag_steps.go @@ -11,6 +11,9 @@ import ( "syscall" "time" + "github.com/sanity-io/litter" + "golang.org/x/sys/unix" + "github.com/foxboron/go-uefi/efi" "github.com/hashicorp/go-multierror" cnst "github.com/kairos-io/immucore/internal/constants" @@ -20,7 +23,6 @@ import ( kcrypt "github.com/kairos-io/kcrypt/pkg/lib" "github.com/mudler/go-kdetect" "github.com/spectrocloud-labs/herd" - "golang.org/x/sys/unix" ) // MountTmpfsDagStep adds the step to mount /tmp . @@ -490,49 +492,18 @@ func (s *State) UKIMountBaseSystem(g *herd.Graph) error { ) } -// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system -// booting to the real init process -// Drops to emergency if not able to. Panic if it cant even launch emergency. -func (s *State) UKIBootInitDagStep(g *herd.Graph) error { - return g.Add(cnst.OpUkiInit, - herd.WeakDeps, - herd.WithWeakDeps(cnst.OpRemountRootRO, cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab), - herd.WithCallback(func(ctx context.Context) error { - output, err := internalUtils.CommandWithPath("/usr/lib/systemd/systemd-pcrphase --graceful leave-initrd") - if err != nil { - internalUtils.Log.Err(err).Msg("running systemd-pcrphase") - internalUtils.Log.Debug().Str("out", output).Msg("systemd-pcrphase leave-initrd") - } - // Print dag before exit, otherwise its never printed as we never exit the program - internalUtils.Log.Info().Msg(s.WriteDAG(g)) - internalUtils.Log.Debug().Msg("Executing init callback!") - internalUtils.CloseLogFiles() - if err := unix.Exec("/sbin/init", []string{"/sbin/init", "--system"}, os.Environ()); err != nil { - internalUtils.Log.Err(err).Msg("running init") - // drop to emergency shell - if err := unix.Exec("/bin/bash", []string{"/bin/bash"}, os.Environ()); err != nil { - internalUtils.Log.Fatal().Msg("Could not drop to emergency shell") - } - } - return nil - })) -} - // UKIRemountRootRODagStep remount root read only. func (s *State) UKIRemountRootRODagStep(g *herd.Graph) error { return g.Add(cnst.OpRemountRootRO, herd.WithDeps(cnst.OpRootfsHook), herd.WithCallback(func(ctx context.Context) error { - var err error - for i := 1; i < 5; i++ { - time.Sleep(1 * time.Second) - // Should we try to stop udev here? - err = syscall.Mount("", "/", "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "") - if err != nil { - continue - } + // Create the /sysroot dir before remounting as RO + err := os.MkdirAll(s.path(cnst.UkiSysrootDir), 0755) + if err != nil { + internalUtils.Log.Err(err).Str("path", s.path(cnst.UkiSysrootDir)).Msg("Creating sysroot") + return err } - return err + return nil }), ) } @@ -808,3 +779,142 @@ func (s *State) MountLiveCd(g *herd.Graph, opts ...herd.OpOption) error { return nil }))...) } + +// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system +// booting to the real init process +// Drops to emergency if not able to. Panic if it cant even launch emergency. +func (s *State) UKIBootInitDagStep(g *herd.Graph) error { + return g.Add(cnst.OpUkiInit, + herd.WeakDeps, + herd.WithWeakDeps(cnst.OpRemountRootRO, cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab), + herd.WithCallback(func(ctx context.Context) error { + output, err := internalUtils.CommandWithPath("/usr/lib/systemd/systemd-pcrphase --graceful leave-initrd") + if err != nil { + internalUtils.Log.Err(err).Msg("running systemd-pcrphase") + internalUtils.Log.Debug().Str("out", output).Msg("systemd-pcrphase leave-initrd") + } + // Print dag before exit, otherwise its never printed as we never exit the program + internalUtils.Log.Info().Msg(s.WriteDAG(g)) + + // Mount a tmpfs under sysroot + err = syscall.Mount("tmpfs", s.path(cnst.UkiSysrootDir), "tmpfs", syscall.MS_NOSUID|syscall.MS_NODEV|syscall.MS_NOEXEC, "") + if err != nil { + internalUtils.Log.Err(err).Msg("mounting tmpfs on sysroot") + } + + // Move all the dirs in root FS that are not a mountpoint to the new root via Bind mount + rootDirs, err := os.ReadDir(s.Rootdir) + if err != nil { + internalUtils.Log.Err(err).Msg("reading rootdir content") + } + mountPoints := []string{} + internalUtils.Log.Debug().Str("s", litter.Sdump(rootDirs)).Msg("Moving root dirs to sysroot") + for _, file := range rootDirs { + if file.Name() == cnst.UkiSysrootDir { + continue + } + if file.IsDir() { + path := file.Name() + fileInfo, err := os.Stat(s.path(path)) + if err != nil { + return err + } + parentPath := filepath.Dir(s.path(path)) + parentInfo, err := os.Stat(parentPath) + if err != nil { + return err + } + // If the directory has the same device as its parent, it's not a mount point. + if fileInfo.Sys().(*syscall.Stat_t).Dev == parentInfo.Sys().(*syscall.Stat_t).Dev { + internalUtils.Log.Debug().Msg(fmt.Sprintf("%s is a simple directory", path)) + err = os.MkdirAll(filepath.Join(s.path(cnst.UkiSysrootDir), path), 0755) + if err != nil { + internalUtils.Log.Err(err).Str("what", filepath.Join(s.path(cnst.UkiSysrootDir), path)).Msg("mkdir") + return err + } + // Bind mount it + err = syscall.Mount(s.path(path), filepath.Join(s.path(cnst.UkiSysrootDir), path), "", syscall.MS_BIND, "") + if err != nil { + internalUtils.Log.Err(err).Str("what", s.path(path)). + Str("where", filepath.Join(s.path(cnst.UkiSysrootDir), path)).Msg("bind mount") + return err + } + internalUtils.Log.Debug().Msg(fmt.Sprintf("Bind mounted %s to %s", s.path(path), filepath.Join(s.path(cnst.UkiSysrootDir), path))) + + continue + } + + internalUtils.Log.Debug().Msg(fmt.Sprintf("%s is a mount point, skipping", s.path(path))) + mountPoints = append(mountPoints, s.path(path)) + + continue + } + + info, _ := file.Info() + fileInfo, _ := os.Lstat(file.Name()) + + // Symlink + if fileInfo.Mode()&os.ModeSymlink != 0 { + target, err := os.Readlink(file.Name()) + if err != nil { + return fmt.Errorf("failed to read symlink: %w", err) + } + symlinkPath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name())) + err = os.Symlink(target, symlinkPath) + if err != nil { + return fmt.Errorf("failed to create symlink: %w", err) + } + internalUtils.Log.Debug().Str("from", target).Str("to", symlinkPath).Msg("Symlinked file") + } else { + // If its a file in the root dir just copy it over + content, _ := os.ReadFile(s.path(file.Name())) + newFilePath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name())) + _ = os.WriteFile(newFilePath, content, info.Mode()) + internalUtils.Log.Debug().Msg(fmt.Sprintf("Copied %s to %s", s.path(file.Name()), newFilePath)) + } + } + // Now move the system mounts into the new dir + for _, d := range mountPoints { + newDir := filepath.Join(s.path(cnst.UkiSysrootDir), d) + err := os.MkdirAll(newDir, 0755) + if err != nil { + internalUtils.Log.Err(err).Str("what", newDir).Msg("mkdir") + } + err = syscall.Mount(filepath.Join(s.path(), d), newDir, "", syscall.MS_MOVE, "") + if err != nil { + internalUtils.Log.Err(err).Str("what", filepath.Join(s.Rootdir, d)).Str("where", newDir).Msg("move mount") + continue + } + internalUtils.Log.Debug().Str("from", filepath.Join(s.path(), d)).Str("to", newDir).Msg("Mount moved") + } + + // remount sysroot as readonly before chrooting + if err = syscall.Mount("", s.path(cnst.UkiSysrootDir), "", syscall.MS_REMOUNT|syscall.MS_RDONLY, ""); err != nil { + internalUtils.Log.Err(err).Msg("re-mounting sysroot as read only") + } + + // Now chdir+chroot into the new dir + if err := unix.Chdir(s.path(cnst.UkiSysrootDir)); err != nil { + internalUtils.Log.Err(err).Msg("chdir") + return fmt.Errorf("failed change directory to new_root %v", err) + } + internalUtils.Log.Debug().Msg("Chdir to sysroot done") + + err = unix.Chroot(".") + if err != nil { + internalUtils.Log.Err(err).Msg("chroot") + return fmt.Errorf("failed to chroot %v", err) + } + internalUtils.Log.Debug().Msg("Chroot to sysroot done") + + internalUtils.Log.Debug().Msg("Executing init callback!") + if err := unix.Exec("/sbin/init", []string{"/sbin/init"}, os.Environ()); err != nil { + internalUtils.Log.Err(err).Msg("running init") + // drop to emergency shell + if err := unix.Exec("/bin/bash", []string{"/bin/bash"}, os.Environ()); err != nil { + internalUtils.Log.Fatal().Msg("Could not drop to emergency shell") + } + } + return nil + })) +}