d1e8bdc2d7
Signed-off-by: Daniele Rondina <geaaru@sabayonlinux.org>
298 lines
5.8 KiB
Go
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
|
|
}
|