distrobuilder/shared/chroot.go
Daniele Rondina d1e8bdc2d7 Permit definition of environment vars on image file
Signed-off-by: Daniele Rondina <geaaru@sabayonlinux.org>
2018-12-22 16:37:37 +01:00

298 lines
5.8 KiB
Go

package shared
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"syscall"
lxd "github.com/lxc/lxd/shared"
)
type chrootMount struct {
source string
target string
fstype string
flags uintptr
data string
isDir bool
}
func setupMounts(rootfs string, mounts []chrootMount) error {
// Create a temporary mount path
err := os.MkdirAll(filepath.Join(rootfs, ".distrobuilder"), 0700)
if err != nil {
return err
}
for i, mount := range mounts {
// Target path
tmpTarget := filepath.Join(rootfs, ".distrobuilder", fmt.Sprintf("%d", i))
// Create the target mountpoint
if mount.isDir {
err := os.Mkdir(tmpTarget, 0755)
if err != nil {
return err
}
} else {
_, err = os.Create(tmpTarget)
if err != nil {
return err
}
}
// Mount to the temporary path
err := syscall.Mount(mount.source, tmpTarget, mount.fstype, mount.flags, mount.data)
if err != nil {
return fmt.Errorf("Failed to mount '%s': %s", mount.source, err)
}
}
return nil
}
func moveMounts(mounts []chrootMount) error {
for i, mount := range mounts {
// Source path
tmpSource := filepath.Join("/", ".distrobuilder", fmt.Sprintf("%d", i))
// Resolve symlinks
target := mount.target
for {
// Get information on current target
fi, err := os.Lstat(target)
if err != nil {
break
}
// If not a symlink, we're done
if fi.Mode()&os.ModeSymlink == 0 {
break
}
// If a symlink, resolve it
newTarget, err := os.Readlink(target)
if err != nil {
break
}
target = newTarget
}
// Create parent paths if missing
err := os.MkdirAll(filepath.Dir(target), 0755)
if err != nil {
return err
}
// Create target path
if mount.isDir {
err = os.MkdirAll(target, 0755)
if err != nil {
return err
}
} else {
_, err = os.Create(target)
if err != nil {
return err
}
}
// Move the mount to its destination
err = syscall.Mount(tmpSource, target, "", syscall.MS_MOVE, "")
if err != nil {
return fmt.Errorf("Failed to mount '%s': %s", mount.source, err)
}
}
// Cleanup our temporary path
err := os.RemoveAll(filepath.Join("/", ".distrobuilder"))
if err != nil {
return err
}
return nil
}
func killChrootProcesses(rootfs string) error {
// List all files under /proc
proc, err := os.Open(filepath.Join(rootfs, "proc"))
if err != nil {
return err
}
dirs, err := proc.Readdirnames(0)
if err != nil {
return err
}
// Get all processes and kill them
for _, dir := range dirs {
match, _ := regexp.MatchString(`\d+`, dir)
if match {
link, _ := os.Readlink(filepath.Join(rootfs, "proc", dir, "root"))
if link == rootfs {
pid, _ := strconv.Atoi(dir)
syscall.Kill(pid, syscall.SIGKILL)
}
}
}
return nil
}
// SetupChroot sets up mount and files, a reverter and then chroots for you
func SetupChroot(rootfs string, envs DefinitionEnv) (func() error, error) {
// Mount the rootfs
err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, "")
if err != nil {
return nil, fmt.Errorf("Failed to mount '%s': %s", rootfs, err)
}
// Setup all other needed mounts
mounts := []chrootMount{
{"none", "/proc", "proc", 0, "", true},
{"none", "/sys", "sysfs", 0, "", true},
{"/dev", "/dev", "", syscall.MS_BIND, "", true},
{"none", "/run", "tmpfs", 0, "", true},
{"none", "/tmp", "tmpfs", 0, "", true},
{"/etc/resolv.conf", "/etc/resolv.conf", "", syscall.MS_BIND, "", false},
}
// Keep a reference to the host rootfs and cwd
root, err := os.Open("/")
if err != nil {
return nil, err
}
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
// Setup all needed mounts in a temporary location
err = setupMounts(rootfs, mounts)
if err != nil {
return nil, fmt.Errorf("Failed to mount filesystems: %v", err)
}
// Chroot into the container's rootfs
err = syscall.Chroot(rootfs)
if err != nil {
root.Close()
return nil, err
}
err = syscall.Chdir("/")
if err != nil {
return nil, err
}
// Move all the mounts into place
err = moveMounts(mounts)
if err != nil {
return nil, err
}
var env Environment
if envs.ClearDefaults {
env = Environment{}
} else {
env = Environment{
"PATH": EnvVariable{
Value: "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin",
Set: true,
},
"SHELL": EnvVariable{
Value: "/bin/sh",
Set: true,
},
"TERM": EnvVariable{
Value: "xterm",
Set: true,
},
"DEBIAN_FRONTEND": EnvVariable{
Value: "noninteractive",
Set: true,
},
}
}
if envs.EnvVariables != nil && len(envs.EnvVariables) > 0 {
for _, e := range envs.EnvVariables {
entry, ok := env[e.Key]
if ok {
entry.Value = e.Value
entry.Set = true
} else {
env[e.Key] = EnvVariable{
Value: e.Value,
Set: true,
}
}
}
}
// Set environment variables
oldEnv := SetEnvVariables(env)
// Setup policy-rc.d override
policyCleanup := false
if lxd.PathExists("/usr/sbin/") && !lxd.PathExists("/usr/sbin/policy-rc.d") {
err = ioutil.WriteFile("/usr/sbin/policy-rc.d", []byte(`#!/bin/sh
exit 101
`), 0755)
if err != nil {
return nil, err
}
policyCleanup = true
}
return func() error {
defer root.Close()
// Cleanup policy-rc.d
if policyCleanup {
err = os.Remove("/usr/sbin/policy-rc.d")
if err != nil {
return err
}
}
// Reset old environment variables
SetEnvVariables(oldEnv)
// Switch back to the host rootfs
err = root.Chdir()
if err != nil {
return err
}
err = syscall.Chroot(".")
if err != nil {
return err
}
err = syscall.Chdir(cwd)
if err != nil {
return err
}
// This will kill all processes in the chroot and allow to cleanly
// unmount everything.
killChrootProcesses(rootfs)
// And now unmount the entire tree
syscall.Unmount(rootfs, syscall.MNT_DETACH)
return nil
}, nil
}