diff options
author | Héctor Orón Martínez <zumbi@debian.org> | 2018-01-09 19:12:07 +0100 |
---|---|---|
committer | Héctor Orón Martínez <zumbi@debian.org> | 2018-01-09 19:12:07 +0100 |
commit | 4808cb7058c548bf76476ec2f9618d784d76bdda (patch) | |
tree | 1dc1e8cc24171783fc8d9da306b1e92798960a15 /commands.go |
New upstream version 1.0.0+git20171222.87b0d5e
Diffstat (limited to 'commands.go')
-rw-r--r-- | commands.go | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..557b636 --- /dev/null +++ b/commands.go @@ -0,0 +1,203 @@ +package debos + +import ( + "bytes" + "fmt" + "io" + "log" + "os" + "os/exec" + "path" +) + +type ChrootEnterMethod int + +const ( + CHROOT_METHOD_NONE = iota // use nspawn to create the chroot environment + CHROOT_METHOD_NSPAWN // No chroot in use + CHROOT_METHOD_CHROOT // use chroot to create the chroot environment +) + +type Command struct { + Architecture string // Architecture of the chroot, nil if same as host + Dir string // Working dir to run command in + Chroot string // Run in the chroot at path + ChrootMethod ChrootEnterMethod // Method to enter the chroot + + bindMounts []string /// Items to bind mount + extraEnv []string // Extra environment variables to set +} + +type commandWrapper struct { + label string + buffer *bytes.Buffer +} + +func newCommandWrapper(label string) *commandWrapper { + b := bytes.Buffer{} + return &commandWrapper{label, &b} +} + +func (w commandWrapper) out(atEOF bool) { + for { + s, err := w.buffer.ReadString('\n') + if err == nil { + log.Printf("%s | %v", w.label, s) + } else { + if len(s) > 0 { + if atEOF && err == io.EOF { + log.Printf("%s | %v\n", w.label, s) + } else { + w.buffer.WriteString(s) + } + } + break + } + } +} + +func (w commandWrapper) Write(p []byte) (n int, err error) { + n, err = w.buffer.Write(p) + w.out(false) + return +} + +func (w *commandWrapper) flush() { + w.out(true) +} + +func NewChrootCommandForContext(context DebosContext) Command { + c := Command{Architecture: context.Architecture, Chroot: context.Rootdir, ChrootMethod: CHROOT_METHOD_NSPAWN} + + if context.Image != "" { + path, err := RealPath(context.Image) + if err == nil { + c.AddBindMount(path, "") + } else { + log.Printf("Failed to get realpath for %s, %v", context.Image, err) + } + for _, p := range context.ImagePartitions { + path, err := RealPath(p.DevicePath) + if err != nil { + log.Printf("Failed to get realpath for %s, %v", p.DevicePath, err) + continue + } + c.AddBindMount(path, "") + } + c.AddBindMount("/dev/disk", "") + } + + return c +} + +func (cmd *Command) AddEnv(env string) { + cmd.extraEnv = append(cmd.extraEnv, env) +} + +func (cmd *Command) AddEnvKey(key, value string) { + cmd.extraEnv = append(cmd.extraEnv, fmt.Sprintf("%s=%s", key, value)) +} + +func (cmd *Command) AddBindMount(source, target string) { + var mount string + if target != "" { + mount = fmt.Sprintf("%s:%s", source, target) + } else { + mount = source + } + + cmd.bindMounts = append(cmd.bindMounts, mount) +} + +func (cmd Command) Run(label string, cmdline ...string) error { + q := newQemuHelper(cmd) + q.Setup() + + var options []string + switch cmd.ChrootMethod { + case CHROOT_METHOD_NONE: + options = cmdline + case CHROOT_METHOD_CHROOT: + options = append(options, "chroot") + options = append(options, cmd.Chroot) + options = append(options, cmdline...) + case CHROOT_METHOD_NSPAWN: + options = append(options, "systemd-nspawn", "-q", "-D", cmd.Chroot) + for _, e := range cmd.extraEnv { + options = append(options, "--setenv", e) + + } + for _, b := range cmd.bindMounts { + options = append(options, "--bind", b) + + } + options = append(options, cmdline...) + } + + exe := exec.Command(options[0], options[1:]...) + w := newCommandWrapper(label) + + exe.Stdin = nil + exe.Stdout = w + exe.Stderr = w + + if len(cmd.extraEnv) > 0 && cmd.ChrootMethod != CHROOT_METHOD_NSPAWN { + exe.Env = append(os.Environ(), cmd.extraEnv...) + } + + // Disable services start/stop for commands running in chroot + if cmd.ChrootMethod != CHROOT_METHOD_NONE { + services := ServiceHelper{cmd.Chroot} + services.Deny() + defer services.Allow() + } + + err := exe.Run() + w.flush() + q.Cleanup() + + return err +} + +type qemuHelper struct { + qemusrc string + qemutarget string +} + +func newQemuHelper(c Command) qemuHelper { + q := qemuHelper{} + + if c.Chroot == "" || c.Architecture == "" { + return q + } + + switch c.Architecture { + case "armhf", "armel", "arm": + q.qemusrc = "/usr/bin/qemu-arm-static" + case "arm64": + q.qemusrc = "/usr/bin/qemu-aarch64-static" + case "amd64", "i386": + /* Dummy, no qemu */ + default: + log.Panicf("Don't know qemu for Architecture %s", c.Architecture) + } + + if q.qemusrc != "" { + q.qemutarget = path.Join(c.Chroot, q.qemusrc) + } + + return q +} + +func (q qemuHelper) Setup() error { + if q.qemusrc == "" { + return nil + } + return CopyFile(q.qemusrc, q.qemutarget, 0755) +} + +func (q qemuHelper) Cleanup() { + if q.qemusrc != "" { + os.Remove(q.qemutarget) + } +} |