diff --git a/pkg/box/exec.go b/pkg/box/exec.go new file mode 100644 index 00000000..78a5f1d9 --- /dev/null +++ b/pkg/box/exec.go @@ -0,0 +1,157 @@ +// Copyright © 2020 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package box + +import ( + b64 "encoding/base64" + "fmt" + "os" + "os/exec" + "syscall" + + "github.com/pkg/errors" + + helpers "github.com/mudler/luet/pkg/helpers" +) + +type Box interface { + Run() error + Exec() error +} + +type DefaultBox struct { + Name string + Root string + Env []string + Cmd string + Args []string + + Stdin, Stdout, Stderr bool +} + +func NewBox(cmd string, args []string, rootfs string, stdin, stdout, stderr bool) Box { + return &DefaultBox{ + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + Cmd: cmd, + Args: args, + Root: rootfs, + } +} + +func (b *DefaultBox) Exec() error { + + if err := mountProc(b.Root); err != nil { + return errors.Wrap(err, "Failed mounting proc on rootfs") + } + if err := mountDev(b.Root); err != nil { + return errors.Wrap(err, "Failed mounting dev on rootfs") + } + if err := PivotRoot(b.Root); err != nil { + return errors.Wrap(err, "Failed switching pivot on rootfs") + } + + cmd := exec.Command(b.Cmd, b.Args...) + + if b.Stdin { + cmd.Stdin = os.Stdin + } + + if b.Stderr { + cmd.Stderr = os.Stderr + } + + if b.Stdout { + cmd.Stdout = os.Stdout + } + + cmd.Env = b.Env + + if err := cmd.Run(); err != nil { + return errors.Wrap(err, fmt.Sprintf("Error running the %s command", b.Cmd)) + } + return nil +} + +func (b *DefaultBox) Run() error { + + if !helpers.Exists(b.Root) { + return errors.New(b.Root + " does not exist") + } + + // This matches with exec CLI command in luet + // TODO: Pass by env var as well + execCmd := []string{"exec", "--rootfs", b.Root, "--entrypoint", b.Cmd} + + if b.Stdin { + execCmd = append(execCmd, "--stdin") + } + + if b.Stderr { + execCmd = append(execCmd, "--stderr") + } + + if b.Stdout { + execCmd = append(execCmd, "--stdout") + } + // Encode the command in base64 to avoid bad input from the args given + execCmd = append(execCmd, "--decode") + + for _, a := range b.Args { + execCmd = append(execCmd, b64.StdEncoding.EncodeToString([]byte(a))) + } + + cmd := exec.Command("/proc/self/exe", execCmd...) + if b.Stdin { + cmd.Stdin = os.Stdin + } + + if b.Stderr { + cmd.Stderr = os.Stderr + } + + if b.Stdout { + cmd.Stdout = os.Stdout + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: syscall.CLONE_NEWNS | + syscall.CLONE_NEWUTS | + syscall.CLONE_NEWIPC | + syscall.CLONE_NEWPID | + syscall.CLONE_NEWNET | + syscall.CLONE_NEWUSER, + UidMappings: []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: os.Getuid(), + Size: 1, + }, + }, + GidMappings: []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: os.Getgid(), + Size: 1, + }, + }, + } + + if err := cmd.Run(); err != nil { + return errors.Wrap(err, "Failed running Box command") + } + return nil +} diff --git a/pkg/box/rootfs.go b/pkg/box/rootfs.go new file mode 100644 index 00000000..415c225d --- /dev/null +++ b/pkg/box/rootfs.go @@ -0,0 +1,91 @@ +// Copyright © 2020 Ettore Di Giacinto +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see . + +package box + +import ( + "os" + "path/filepath" + "syscall" +) + +func PivotRoot(newroot string) error { + putold := filepath.Join(newroot, "/.pivot_root") + + // bind mount newroot to itself - this is a slight hack needed to satisfy the + // pivot_root requirement that newroot and putold must not be on the same + // filesystem as the current root + if err := syscall.Mount(newroot, newroot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { + return err + } + + // create putold directory + if err := os.MkdirAll(putold, 0700); err != nil { + return err + } + + // call pivot_root + if err := syscall.PivotRoot(newroot, putold); err != nil { + return err + } + + // ensure current working directory is set to new root + if err := os.Chdir("/"); err != nil { + return err + } + + // umount putold, which now lives at /.pivot_root + putold = "/.pivot_root" + if err := syscall.Unmount(putold, syscall.MNT_DETACH); err != nil { + return err + } + + // remove putold + if err := os.RemoveAll(putold); err != nil { + return err + } + + return nil +} + +func mountProc(newroot string) error { + source := "proc" + target := filepath.Join(newroot, "/proc") + fstype := "proc" + flags := 0 + data := "" + + os.MkdirAll(target, 0755) + if err := syscall.Mount(source, target, fstype, uintptr(flags), data); err != nil { + return err + } + + return nil +} + +func mountDev(newroot string) error { + + source := "/dev" + target := filepath.Join(newroot, "/dev") + fstype := "bind" + data := "" + + os.MkdirAll(target, 0755) + if err := syscall.Mount(source, target, fstype, syscall.MS_BIND|syscall.MS_REC, data); err != nil { + return err + } + + return nil +}