Path: blob/main/components/workspacekit/pkg/lift/lift.go
2500 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package lift56// Lift can execute commands in a namespace context and return the stdin/out/err FDs7// to the caller. This allows us to lift commands into ring1, akin to `docker exec`.89import (10"bufio"11"bytes"12"context"13"encoding/json"14"fmt"15"net"16"os"17"os/exec"18"time"1920"github.com/gitpod-io/gitpod/common-go/log"21"golang.org/x/sys/unix"22"golang.org/x/xerrors"23)2425const (26DefaultSocketPath = "/tmp/workspacekit-lift.socket"27)2829type LiftRequest struct {30Command []string `json:"command"`31}3233func ServeLift(ctx context.Context, socket string) error {34skt, err := net.Listen("unix", socket)35if err != nil {36return err37}3839defer func() {40err := skt.Close()41if err != nil {42log.WithError(err).Error("unexpected error closing listener")43}44}()4546for {47select {48case <-ctx.Done():49break50default:51// pass through52}5354conn, err := skt.Accept()55if err != nil {56log.WithError(err).Error("cannot accept lift connection")57continue58}5960go func() {61err := serveLiftClient(conn)62if err != nil {63log.WithError(err).Error("cannot serve lift connection")64}65}()66}67}6869func serveLiftClient(conn net.Conn) error {70unixConn := conn.(*net.UnixConn)7172f, err := unixConn.File()73if err != nil {74return err75}7677defer func() {78err := f.Close()79if err != nil {80log.WithError(err).Error("unexpected error closing connection")81}8283err = conn.Close()84if err != nil {85log.WithError(err).Error("unexpected error closing connection")86}87}()8889buf := make([]byte, unix.CmsgSpace(3*4)) // we expect 3 FDs90_, _, _, _, err = unix.Recvmsg(int(f.Fd()), nil, buf, 0)91if err != nil {92return err93}9495msgs, err := unix.ParseSocketControlMessage(buf)96if err != nil {97return err98}99100if len(msgs) != 1 {101return xerrors.Errorf("expected a single socket control message")102}103104fds, err := unix.ParseUnixRights(&msgs[0])105if err != nil {106return err107}108109if len(fds) != 3 {110return xerrors.Errorf("expected three file descriptors")111}112113rd := bufio.NewReader(f)114line, err := rd.ReadBytes('\n')115if err != nil {116return err117}118119var msg LiftRequest120err = json.Unmarshal(line, &msg)121if err != nil {122return err123}124125if len(msg.Command) == 0 {126return xerrors.Errorf("expected non-empty command")127}128129log.WithField("command", msg.Command).Info("running lifted command")130131cmd := exec.Command(msg.Command[0], msg.Command[1:]...)132cmd.SysProcAttr = &unix.SysProcAttr{133Setpgid: true,134}135cmd.Stdout = os.NewFile(uintptr(fds[0]), "stdout")136cmd.Stderr = os.NewFile(uintptr(fds[1]), "stderr")137cmd.Stdin = os.NewFile(uintptr(fds[2]), "stdin")138139err = cmd.Start()140if err != nil {141return err142}143144defer func() {145err := cmd.Process.Kill()146if err != nil && err.Error() != "os: process already finished" {147log.WithError(err).Error("unexpected error terminating process")148}149}()150151err = cmd.Wait()152if err != nil {153log.WithError(err).Error("unexpected error running process")154}155156return nil157}158159func RunCommand(socket string, command []string) error {160rconn, err := net.Dial("unix", socket)161if err != nil {162return err163}164165conn := rconn.(*net.UnixConn)166f, err := conn.File()167if err != nil {168return err169}170171defer func() {172err := f.Close()173if err != nil {174log.WithError(err).Error("unexpected error closing lift connection")175}176}()177178err = unix.Sendmsg(int(f.Fd()), nil, unix.UnixRights(int(os.Stdout.Fd()), int(os.Stderr.Fd()), int(os.Stdin.Fd())), nil, 0)179if err != nil {180return err181}182183msg, err := json.Marshal(LiftRequest{Command: command})184if err != nil {185return err186}187188_, err = conn.Write(msg)189if err != nil {190return err191}192193_, err = conn.Write([]byte{'\n'})194if err != nil {195return err196}197198buf := make([]byte, 128)199for n, err := conn.Read(buf); err == nil; n, err = conn.Read(buf) {200if bytes.Equal([]byte("done"), buf[:n]) {201break202}203204time.Sleep(10 * time.Millisecond)205}206fmt.Println("done")207208return nil209}210211212