Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/workspacekit/pkg/lift/lift.go
2500 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package lift
6
7
// Lift can execute commands in a namespace context and return the stdin/out/err FDs
8
// to the caller. This allows us to lift commands into ring1, akin to `docker exec`.
9
10
import (
11
"bufio"
12
"bytes"
13
"context"
14
"encoding/json"
15
"fmt"
16
"net"
17
"os"
18
"os/exec"
19
"time"
20
21
"github.com/gitpod-io/gitpod/common-go/log"
22
"golang.org/x/sys/unix"
23
"golang.org/x/xerrors"
24
)
25
26
const (
27
DefaultSocketPath = "/tmp/workspacekit-lift.socket"
28
)
29
30
type LiftRequest struct {
31
Command []string `json:"command"`
32
}
33
34
func ServeLift(ctx context.Context, socket string) error {
35
skt, err := net.Listen("unix", socket)
36
if err != nil {
37
return err
38
}
39
40
defer func() {
41
err := skt.Close()
42
if err != nil {
43
log.WithError(err).Error("unexpected error closing listener")
44
}
45
}()
46
47
for {
48
select {
49
case <-ctx.Done():
50
break
51
default:
52
// pass through
53
}
54
55
conn, err := skt.Accept()
56
if err != nil {
57
log.WithError(err).Error("cannot accept lift connection")
58
continue
59
}
60
61
go func() {
62
err := serveLiftClient(conn)
63
if err != nil {
64
log.WithError(err).Error("cannot serve lift connection")
65
}
66
}()
67
}
68
}
69
70
func serveLiftClient(conn net.Conn) error {
71
unixConn := conn.(*net.UnixConn)
72
73
f, err := unixConn.File()
74
if err != nil {
75
return err
76
}
77
78
defer func() {
79
err := f.Close()
80
if err != nil {
81
log.WithError(err).Error("unexpected error closing connection")
82
}
83
84
err = conn.Close()
85
if err != nil {
86
log.WithError(err).Error("unexpected error closing connection")
87
}
88
}()
89
90
buf := make([]byte, unix.CmsgSpace(3*4)) // we expect 3 FDs
91
_, _, _, _, err = unix.Recvmsg(int(f.Fd()), nil, buf, 0)
92
if err != nil {
93
return err
94
}
95
96
msgs, err := unix.ParseSocketControlMessage(buf)
97
if err != nil {
98
return err
99
}
100
101
if len(msgs) != 1 {
102
return xerrors.Errorf("expected a single socket control message")
103
}
104
105
fds, err := unix.ParseUnixRights(&msgs[0])
106
if err != nil {
107
return err
108
}
109
110
if len(fds) != 3 {
111
return xerrors.Errorf("expected three file descriptors")
112
}
113
114
rd := bufio.NewReader(f)
115
line, err := rd.ReadBytes('\n')
116
if err != nil {
117
return err
118
}
119
120
var msg LiftRequest
121
err = json.Unmarshal(line, &msg)
122
if err != nil {
123
return err
124
}
125
126
if len(msg.Command) == 0 {
127
return xerrors.Errorf("expected non-empty command")
128
}
129
130
log.WithField("command", msg.Command).Info("running lifted command")
131
132
cmd := exec.Command(msg.Command[0], msg.Command[1:]...)
133
cmd.SysProcAttr = &unix.SysProcAttr{
134
Setpgid: true,
135
}
136
cmd.Stdout = os.NewFile(uintptr(fds[0]), "stdout")
137
cmd.Stderr = os.NewFile(uintptr(fds[1]), "stderr")
138
cmd.Stdin = os.NewFile(uintptr(fds[2]), "stdin")
139
140
err = cmd.Start()
141
if err != nil {
142
return err
143
}
144
145
defer func() {
146
err := cmd.Process.Kill()
147
if err != nil && err.Error() != "os: process already finished" {
148
log.WithError(err).Error("unexpected error terminating process")
149
}
150
}()
151
152
err = cmd.Wait()
153
if err != nil {
154
log.WithError(err).Error("unexpected error running process")
155
}
156
157
return nil
158
}
159
160
func RunCommand(socket string, command []string) error {
161
rconn, err := net.Dial("unix", socket)
162
if err != nil {
163
return err
164
}
165
166
conn := rconn.(*net.UnixConn)
167
f, err := conn.File()
168
if err != nil {
169
return err
170
}
171
172
defer func() {
173
err := f.Close()
174
if err != nil {
175
log.WithError(err).Error("unexpected error closing lift connection")
176
}
177
}()
178
179
err = unix.Sendmsg(int(f.Fd()), nil, unix.UnixRights(int(os.Stdout.Fd()), int(os.Stderr.Fd()), int(os.Stdin.Fd())), nil, 0)
180
if err != nil {
181
return err
182
}
183
184
msg, err := json.Marshal(LiftRequest{Command: command})
185
if err != nil {
186
return err
187
}
188
189
_, err = conn.Write(msg)
190
if err != nil {
191
return err
192
}
193
194
_, err = conn.Write([]byte{'\n'})
195
if err != nil {
196
return err
197
}
198
199
buf := make([]byte, 128)
200
for n, err := conn.Read(buf); err == nil; n, err = conn.Read(buf) {
201
if bytes.Equal([]byte("done"), buf[:n]) {
202
break
203
}
204
205
time.Sleep(10 * time.Millisecond)
206
}
207
fmt.Println("done")
208
209
return nil
210
}
211
212