Skip to content

Commit

Permalink
Merge pull request #1833 from stgraber/forkexec
Browse files Browse the repository at this point in the history
Use fork for command execution
  • Loading branch information
hallyn committed Mar 31, 2016
2 parents 62ca746 + d76c549 commit b08868e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 58 deletions.
3 changes: 1 addition & 2 deletions lxc/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ func (c *execCmd) run(config *lxd.Config, args []string) error {
termios.Restore(cfd, oldttystate)
}

/* we get the result of waitpid() here so we need to transform it */
os.Exit(ret >> 8)
os.Exit(ret)
return fmt.Errorf(i18n.G("unreachable return reached"))
}
4 changes: 3 additions & 1 deletion lxd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ type container interface {
FilePull(srcpath string, dstpath string) error
FilePush(srcpath string, dstpath string, uid int, gid int, mode os.FileMode) error

// Command execution
Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error)

// Status
Render() (interface{}, error)
RenderState() (*shared.ContainerState, error)
Expand Down Expand Up @@ -383,7 +386,6 @@ type container interface {
LogPath() string

// FIXME: Those should be internal functions
LXContainerGet() *lxc.Container
StorageStart() error
StorageStop() error
Storage() storage
Expand Down
65 changes: 24 additions & 41 deletions lxd/container_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"gopkg.in/lxc/go-lxc.v2"

"github.com/lxc/lxd/shared"
)
Expand All @@ -26,22 +25,13 @@ type commandPostContent struct {
Height int `json:"height"`
}

func runCommand(container *lxc.Container, command []string, options lxc.AttachOptions) (int, error) {
status, err := container.RunCommandStatus(command, options)
if err != nil {
shared.Debugf("Failed running command: %q", err.Error())
return 0, err
}

return status, nil
}

type execWs struct {
command []string
container *lxc.Container
command []string
container container
env map[string]string

rootUid int
rootGid int
options lxc.AttachOptions
conns map[int]*websocket.Conn
connsLock sync.Mutex
allConnected chan bool
Expand Down Expand Up @@ -109,13 +99,18 @@ func (s *execWs) Do(op *operation) error {
var ttys []*os.File
var ptys []*os.File

var stdin *os.File
var stdout *os.File
var stderr *os.File

if s.interactive {
ttys = make([]*os.File, 1)
ptys = make([]*os.File, 1)
ptys[0], ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
s.options.StdinFd = ttys[0].Fd()
s.options.StdoutFd = ttys[0].Fd()
s.options.StderrFd = ttys[0].Fd()

stdin = ttys[0]
stdout = ttys[0]
stderr = ttys[0]

if s.width > 0 && s.height > 0 {
shared.SetSize(int(ptys[0].Fd()), s.width, s.height)
Expand All @@ -129,9 +124,10 @@ func (s *execWs) Do(op *operation) error {
return err
}
}
s.options.StdinFd = ptys[0].Fd()
s.options.StdoutFd = ttys[1].Fd()
s.options.StderrFd = ttys[2].Fd()

stdin = ptys[0]
stdout = ttys[1]
stderr = ttys[2]
}

controlExit := make(chan bool)
Expand Down Expand Up @@ -216,11 +212,7 @@ func (s *execWs) Do(op *operation) error {
}
}

cmdResult, cmdErr := runCommand(
s.container,
s.command,
s.options,
)
cmdResult, cmdErr := s.container.Exec(s.command, s.env, stdin, stdout, stderr)

for _, tty := range ttys {
tty.Close()
Expand Down Expand Up @@ -274,22 +266,17 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
return BadRequest(err)
}

opts := lxc.DefaultAttachOptions
opts.ClearEnv = true
opts.Env = []string{}
env := map[string]string{}

for k, v := range c.ExpandedConfig() {
if strings.HasPrefix(k, "environment.") {
opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", strings.TrimPrefix(k, "environment."), v))
env[strings.TrimPrefix(k, "environment.")] = v
}
}

if post.Environment != nil {
for k, v := range post.Environment {
if k == "HOME" {
opts.Cwd = v
}
opts.Env = append(opts.Env, fmt.Sprintf("%s=%s", k, v))
env[k] = v
}
}

Expand All @@ -310,7 +297,6 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
ws.allConnected = make(chan bool, 1)
ws.controlConnected = make(chan bool, 1)
ws.interactive = post.Interactive
ws.options = opts
for i := -1; i < len(ws.conns)-1; i++ {
ws.fds[i], err = shared.RandomCryptoString()
if err != nil {
Expand All @@ -319,7 +305,9 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
}

ws.command = post.Command
ws.container = c.LXContainerGet()
ws.container = c
ws.env = env

ws.width = post.Width
ws.height = post.Height

Expand All @@ -340,13 +328,8 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
return err
}
defer nullDev.Close()
nullfd := nullDev.Fd()

opts.StdinFd = nullfd
opts.StdoutFd = nullfd
opts.StderrFd = nullfd

_, cmdErr := runCommand(c.LXContainerGet(), post.Command, opts)
_, cmdErr := c.Exec(post.Command, env, nil, nil, nil)
return cmdErr
}

Expand Down
52 changes: 40 additions & 12 deletions lxd/container_lxc.go
Original file line number Diff line number Diff line change
Expand Up @@ -2921,6 +2921,46 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
return nil
}

func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
envSlice := []string{}

for k, v := range env {
envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
}

args := []string{c.daemon.execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}

args = append(args, "--")
args = append(args, "env")
args = append(args, envSlice...)

args = append(args, "--")
args = append(args, "cmd")
args = append(args, command...)

cmd := exec.Cmd{}
cmd.Path = c.daemon.execPath
cmd.Args = args
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr

err := cmd.Run()
if err != nil {
exitErr, ok := err.(*exec.ExitError)
if ok {
status, ok := exitErr.Sys().(syscall.WaitStatus)
if ok {
return status.ExitStatus(), nil
}
}

return -1, err
}

return 0, nil
}

func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
disk := map[string]shared.ContainerStateDisk{}

Expand Down Expand Up @@ -4332,18 +4372,6 @@ func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
return lastIdmap, nil
}

func (c *containerLXC) LXContainerGet() *lxc.Container {
// FIXME: This function should go away

// Load the go-lxc struct
err := c.initLXC()
if err != nil {
return nil
}

return c.c
}

func (c *containerLXC) Daemon() *Daemon {
// FIXME: This function should go away
return c.daemon
Expand Down
89 changes: 87 additions & 2 deletions lxd/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -198,8 +199,6 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
* This is called by lxd when called as "lxd forkstart <container>"
* 'forkstart' is used instead of just 'start' in the hopes that people
* do not accidentally type 'lxd start' instead of 'lxc start'
*
* We expect to read the lxcconfig over fd 3.
*/
func startContainer(args []string) error {
if len(args) != 4 {
Expand Down Expand Up @@ -246,3 +245,89 @@ func startContainer(args []string) error {

return c.Start()
}

/*
* This is called by lxd when called as "lxd forkexec <container>"
*/
func execContainer(args []string) (int, error) {
if len(args) < 6 {
return -1, fmt.Errorf("Bad arguments: %q", args)
}

name := args[1]
lxcpath := args[2]
configPath := args[3]

c, err := lxc.NewContainer(name, lxcpath)
if err != nil {
return -1, fmt.Errorf("Error initializing container for start: %q", err)
}

err = c.LoadConfigFile(configPath)
if err != nil {
return -1, fmt.Errorf("Error opening startup config file: %q", err)
}

syscall.Dup3(int(os.Stdin.Fd()), 200, 0)
syscall.Dup3(int(os.Stdout.Fd()), 201, 0)
syscall.Dup3(int(os.Stderr.Fd()), 202, 0)

syscall.Close(int(os.Stdin.Fd()))
syscall.Close(int(os.Stdout.Fd()))
syscall.Close(int(os.Stderr.Fd()))

opts := lxc.DefaultAttachOptions
opts.ClearEnv = true
opts.StdinFd = 200
opts.StdoutFd = 201
opts.StderrFd = 202

logPath := shared.LogPath(name, "forkexec.log")
if shared.PathExists(logPath) {
os.Remove(logPath)
}

logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
if err == nil {
syscall.Dup3(int(logFile.Fd()), 1, 0)
syscall.Dup3(int(logFile.Fd()), 2, 0)
}

env := []string{}
cmd := []string{}

section := ""
for _, arg := range args[5:len(args)] {
// The "cmd" section must come last as it may contain a --
if arg == "--" && section != "cmd" {
section = ""
continue
}

if section == "" {
section = arg
continue
}

if section == "env" {
fields := strings.SplitN(arg, "=", 2)
if len(fields) == 2 && fields[0] == "HOME" {
opts.Cwd = fields[1]
}
env = append(env, arg)
} else if section == "cmd" {
cmd = append(cmd, arg)
} else {
return -1, fmt.Errorf("Invalid exec section: %s", section)
}
}

opts.Env = env

status, err := c.RunCommandStatus(cmd, opts)
if err != nil {
return -1, fmt.Errorf("Failed running command: %q", err)
}

return status >> 8, nil
}
8 changes: 8 additions & 0 deletions lxd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ func run() error {
fmt.Printf(" How long to wait before failing\n")

fmt.Printf("\n\nInternal commands (don't call these directly):\n")
fmt.Printf(" forkexec\n")
fmt.Printf(" Execute a command in a container\n")
fmt.Printf(" forkgetnet\n")
fmt.Printf(" Get container network information\n")
fmt.Printf(" forkgetfile\n")
Expand Down Expand Up @@ -219,6 +221,12 @@ func run() error {
return MigrateContainer(os.Args[1:])
case "forkstart":
return startContainer(os.Args[1:])
case "forkexec":
ret, err := execContainer(os.Args[1:])
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
os.Exit(ret)
}
}

Expand Down

0 comments on commit b08868e

Please sign in to comment.