func (s *execWs) Do(id string) shared.OperationResult { <-s.allConnected var err error var ttys []*os.File var ptys []*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() } else { ttys = make([]*os.File, 3) ptys = make([]*os.File, 3) for i := 0; i < len(ttys); i++ { ptys[i], ttys[i], err = shared.Pipe() if err != nil { return shared.OperationError(err) } } s.options.StdinFd = ptys[0].Fd() s.options.StdoutFd = ttys[1].Fd() s.options.StderrFd = ttys[2].Fd() } controlExit := make(chan bool) var wgEOF sync.WaitGroup if s.interactive { wgEOF.Add(1) go func() { select { case <-s.controlConnected: break case <-controlExit: return } for { mt, r, err := s.conns[-1].NextReader() if mt == websocket.CloseMessage { break } if err != nil { shared.Debugf("Got error getting next reader %s", err) break } buf, err := ioutil.ReadAll(r) if err != nil { shared.Debugf("Failed to read message %s", err) break } command := shared.ContainerExecControl{} if err := json.Unmarshal(buf, &command); err != nil { shared.Debugf("Failed to unmarshal control socket command: %s", err) continue } if command.Command == "window-resize" { winchWidth, err := strconv.Atoi(command.Args["width"]) if err != nil { shared.Debugf("Unable to extract window width: %s", err) continue } winchHeight, err := strconv.Atoi(command.Args["height"]) if err != nil { shared.Debugf("Unable to extract window height: %s", err) continue } err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight) if err != nil { shared.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight) continue } } if err != nil { shared.Debugf("Got error writing to writer %s", err) break } } }() go func() { <-shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0]) wgEOF.Done() }() } else { wgEOF.Add(len(ttys) - 1) for i := 0; i < len(ttys); i++ { go func(i int) { if i == 0 { <-shared.WebsocketRecvStream(ttys[i], s.conns[i]) ttys[i].Close() } else { <-shared.WebsocketSendStream(s.conns[i], ptys[i]) ptys[i].Close() wgEOF.Done() } }(i) } } result := runCommand( s.container, s.command, s.options, ) for _, tty := range ttys { tty.Close() } if s.conns[-1] == nil { if s.interactive { controlExit <- true } } else { s.conns[-1].Close() } wgEOF.Wait() for _, pty := range ptys { pty.Close() } return result }
func (s *execWs) Do(op *operation) error { <-s.allConnected var err 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) 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) } } else { ttys = make([]*os.File, 3) ptys = make([]*os.File, 3) for i := 0; i < len(ttys); i++ { ptys[i], ttys[i], err = shared.Pipe() if err != nil { return err } } stdin = ptys[0] stdout = ttys[1] stderr = ttys[2] } controlExit := make(chan bool) receivePid := make(chan int) var wgEOF sync.WaitGroup if s.interactive { wgEOF.Add(1) go func() { receivedPid := <-receivePid select { case <-s.controlConnected: break case <-controlExit: return } for { mt, r, err := s.conns[-1].NextReader() if mt == websocket.CloseMessage { break } if err != nil { shared.LogDebugf("Got error getting next reader %s", err) break } buf, err := ioutil.ReadAll(r) if err != nil { shared.LogDebugf("Failed to read message %s", err) break } command := shared.ContainerExecControl{} if err := json.Unmarshal(buf, &command); err != nil { shared.LogDebugf("Failed to unmarshal control socket command: %s", err) continue } if command.Command == "window-resize" { winchWidth, err := strconv.Atoi(command.Args["width"]) if err != nil { shared.LogDebugf("Unable to extract window width: %s", err) continue } winchHeight, err := strconv.Atoi(command.Args["height"]) if err != nil { shared.LogDebugf("Unable to extract window height: %s", err) continue } err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight) if err != nil { shared.LogDebugf("Failed to set window size to: %dx%d", winchWidth, winchHeight) continue } } else if command.Command == "signal" { if err := syscall.Kill(receivedPid, command.Signal); err != nil { shared.LogDebugf("Failed forwarding signal '%s' to PID %d.", command.Signal, receivedPid) continue } shared.LogDebugf("Forwarded signal '%s' to PID %d.", command.Signal, receivedPid) } } }() go func() { readDone, writeDone := shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0]) <-readDone <-writeDone s.conns[0].Close() wgEOF.Done() }() } else { wgEOF.Add(len(ttys) - 1) for i := 0; i < len(ttys); i++ { go func(i int) { if i == 0 { <-shared.WebsocketRecvStream(ttys[i], s.conns[i]) ttys[i].Close() } else { <-shared.WebsocketSendStream(s.conns[i], ptys[i], -1) ptys[i].Close() wgEOF.Done() } }(i) } } finisher := func(cmdResult int, cmdErr error) error { for _, tty := range ttys { tty.Close() } if s.conns[-1] == nil { if s.interactive { controlExit <- true } } else { s.conns[-1].Close() } wgEOF.Wait() for _, pty := range ptys { pty.Close() } metadata := shared.Jmap{"return": cmdResult} err = op.UpdateMetadata(metadata) if err != nil { return err } return cmdErr } r, w, err := shared.Pipe() defer r.Close() if err != nil { shared.LogErrorf("s", err) return err } cmd, err := s.container.ExecNoWait(s.command, s.env, stdin, stdout, stderr, w) if err != nil { w.Close() return err } err = cmd.Start() if err != nil { w.Close() return err } w.Close() attachedPid := -1 if err := json.NewDecoder(r).Decode(&attachedPid); err != nil { shared.LogErrorf("Failed to retrieve PID of executing child process: %s", err) return finisher(-1, err) } if s.interactive { receivePid <- attachedPid } err = cmd.Wait() if err != nil { exitErr, ok := err.(*exec.ExitError) if ok { status, ok := exitErr.Sys().(syscall.WaitStatus) if ok { return finisher(status.ExitStatus(), nil) } } } return finisher(0, nil) }
func (s *execWs) Do(op *operation) error { <-s.allConnected var err 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) 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) } } else { ttys = make([]*os.File, 3) ptys = make([]*os.File, 3) for i := 0; i < len(ttys); i++ { ptys[i], ttys[i], err = shared.Pipe() if err != nil { return err } } stdin = ptys[0] stdout = ttys[1] stderr = ttys[2] } controlExit := make(chan bool) var wgEOF sync.WaitGroup if s.interactive { wgEOF.Add(1) go func() { select { case <-s.controlConnected: break case <-controlExit: return } for { mt, r, err := s.conns[-1].NextReader() if mt == websocket.CloseMessage { break } if err != nil { shared.Debugf("Got error getting next reader %s", err) break } buf, err := ioutil.ReadAll(r) if err != nil { shared.Debugf("Failed to read message %s", err) break } command := shared.ContainerExecControl{} if err := json.Unmarshal(buf, &command); err != nil { shared.Debugf("Failed to unmarshal control socket command: %s", err) continue } if command.Command == "window-resize" { winchWidth, err := strconv.Atoi(command.Args["width"]) if err != nil { shared.Debugf("Unable to extract window width: %s", err) continue } winchHeight, err := strconv.Atoi(command.Args["height"]) if err != nil { shared.Debugf("Unable to extract window height: %s", err) continue } err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight) if err != nil { shared.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight) continue } } } }() go func() { readDone, writeDone := shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0]) <-readDone <-writeDone s.conns[0].Close() wgEOF.Done() }() } else { wgEOF.Add(len(ttys) - 1) for i := 0; i < len(ttys); i++ { go func(i int) { if i == 0 { <-shared.WebsocketRecvStream(ttys[i], s.conns[i]) ttys[i].Close() } else { <-shared.WebsocketSendStream(s.conns[i], ptys[i]) ptys[i].Close() wgEOF.Done() } }(i) } } cmdResult, cmdErr := s.container.Exec(s.command, s.env, stdin, stdout, stderr) for _, tty := range ttys { tty.Close() } if s.conns[-1] == nil { if s.interactive { controlExit <- true } } else { s.conns[-1].Close() } wgEOF.Wait() for _, pty := range ptys { pty.Close() } metadata := shared.Jmap{"return": cmdResult} err = op.UpdateMetadata(metadata) if err != nil { return err } return cmdErr }
func (s *execWs) Do(op *operation) error { <-s.allConnected var err 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) 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) } } else { ttys = make([]*os.File, 3) ptys = make([]*os.File, 3) for i := 0; i < len(ttys); i++ { ptys[i], ttys[i], err = shared.Pipe() if err != nil { return err } } stdin = ptys[0] stdout = ttys[1] stderr = ttys[2] } controlExit := make(chan bool) receivePid := make(chan int) var wgEOF sync.WaitGroup if s.interactive { wgEOF.Add(1) go func() { receivedPid := <-receivePid select { case <-s.controlConnected: break case <-controlExit: return } for { mt, r, err := s.conns[-1].NextReader() if mt == websocket.CloseMessage { break } if err != nil { shared.LogDebugf("Got error getting next reader %s", err) break } buf, err := ioutil.ReadAll(r) if err != nil { shared.LogDebugf("Failed to read message %s", err) break } command := shared.ContainerExecControl{} if err := json.Unmarshal(buf, &command); err != nil { shared.LogDebugf("Failed to unmarshal control socket command: %s", err) continue } if command.Command == "window-resize" { winchWidth, err := strconv.Atoi(command.Args["width"]) if err != nil { shared.LogDebugf("Unable to extract window width: %s", err) continue } winchHeight, err := strconv.Atoi(command.Args["height"]) if err != nil { shared.LogDebugf("Unable to extract window height: %s", err) continue } err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight) if err != nil { shared.LogDebugf("Failed to set window size to: %dx%d", winchWidth, winchHeight) continue } } else if command.Command == "signal" { if err := syscall.Kill(receivedPid, command.Signal); err != nil { shared.LogDebugf("Failed forwarding signal '%s' to PID %d.", command.Signal, receivedPid) continue } shared.LogDebugf("Forwarded signal '%s' to PID %d.", command.Signal, receivedPid) } } }() go func() { readDone, writeDone := shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0]) <-readDone <-writeDone s.conns[0].Close() wgEOF.Done() }() } else { wgEOF.Add(len(ttys) - 1) for i := 0; i < len(ttys); i++ { go func(i int) { if i == 0 { <-shared.WebsocketRecvStream(ttys[i], s.conns[i]) ttys[i].Close() } else { <-shared.WebsocketSendStream(s.conns[i], ptys[i], -1) ptys[i].Close() wgEOF.Done() } }(i) } } finisher := func(cmdResult int, cmdErr error) error { for _, tty := range ttys { tty.Close() } if s.conns[-1] == nil { if s.interactive { controlExit <- true } } else { s.conns[-1].Close() } wgEOF.Wait() for _, pty := range ptys { pty.Close() } metadata := shared.Jmap{"return": cmdResult} err = op.UpdateMetadata(metadata) if err != nil { return err } return cmdErr } pid, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false) if err != nil { return err } if s.interactive { receivePid <- attachedPid } proc, err := os.FindProcess(pid) if err != nil { return finisher(-1, fmt.Errorf("Failed finding process: %q", err)) } procState, err := proc.Wait() if err != nil { return finisher(-1, fmt.Errorf("Failed waiting on process %d: %q", pid, err)) } if procState.Success() { return finisher(0, nil) } status, ok := procState.Sys().(syscall.WaitStatus) if ok { if status.Exited() { return finisher(status.ExitStatus(), nil) } // Backwards compatible behavior. Report success when we exited // due to a signal. Otherwise this may break Jenkins, e.g. when // lxc exec foo reboot receives SIGTERM and status.Exitstats() // would report -1. if status.Signaled() { return finisher(0, nil) } } return finisher(-1, nil) }