Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/nsinsider/nsinsider.go
2499 views
1
// Copyright (c) 2022 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 nsinsider
6
7
import (
8
"bytes"
9
"fmt"
10
"io"
11
"os"
12
"os/exec"
13
"path/filepath"
14
15
"github.com/gitpod-io/gitpod/common-go/log"
16
"golang.org/x/sys/unix"
17
)
18
19
type NsinsiderOpts struct {
20
MountNS bool
21
PidNS bool
22
NetNS bool
23
MountNSPid int
24
}
25
26
func EnterMountNS(enter bool) nsinsiderOpt {
27
return func(o *NsinsiderOpts) {
28
o.MountNS = enter
29
}
30
}
31
32
func EnterPidNS(enter bool) nsinsiderOpt {
33
return func(o *NsinsiderOpts) {
34
o.PidNS = enter
35
}
36
}
37
38
func EnterNetNS(enter bool) nsinsiderOpt {
39
return func(o *NsinsiderOpts) {
40
o.NetNS = enter
41
}
42
}
43
44
func EnterMountNSPid(pid int) nsinsiderOpt {
45
return func(o *NsinsiderOpts) {
46
o.MountNS = true
47
o.MountNSPid = pid
48
}
49
}
50
51
type nsinsiderOpt func(*NsinsiderOpts)
52
53
func Nsinsider(instanceID string, targetPid int, mod func(*exec.Cmd), opts ...nsinsiderOpt) error {
54
cfg := NsinsiderOpts{
55
MountNS: true,
56
}
57
for _, o := range opts {
58
o(&cfg)
59
}
60
61
base, err := os.Executable()
62
if err != nil {
63
return err
64
}
65
66
type mnt struct {
67
Env string
68
Source string
69
Flags int
70
}
71
var nss []mnt
72
if cfg.MountNS {
73
tpid := targetPid
74
if cfg.MountNSPid != 0 {
75
tpid = cfg.MountNSPid
76
}
77
nss = append(nss,
78
mnt{"_LIBNSENTER_ROOTFD", fmt.Sprintf("/proc/%d/root", tpid), unix.O_PATH},
79
mnt{"_LIBNSENTER_CWDFD", fmt.Sprintf("/proc/%d/cwd", tpid), unix.O_PATH},
80
mnt{"_LIBNSENTER_MNTNSFD", fmt.Sprintf("/proc/%d/ns/mnt", tpid), os.O_RDONLY},
81
)
82
}
83
if cfg.PidNS {
84
nss = append(nss, mnt{"_LIBNSENTER_PIDNSFD", fmt.Sprintf("/proc/%d/ns/pid", targetPid), os.O_RDONLY})
85
}
86
if cfg.NetNS {
87
nss = append(nss, mnt{"_LIBNSENTER_NETNSFD", fmt.Sprintf("/proc/%d/ns/net", targetPid), os.O_RDONLY})
88
}
89
90
stdioFdCount := 3
91
cmd := exec.Command(filepath.Join(filepath.Dir(base), "nsinsider"))
92
mod(cmd)
93
cmd.Env = append(cmd.Env, "_LIBNSENTER_INIT=1", "GITPOD_INSTANCE_ID="+instanceID)
94
for _, ns := range nss {
95
f, err := os.OpenFile(ns.Source, ns.Flags, 0)
96
if err != nil {
97
return fmt.Errorf("cannot open %s: %w", ns.Source, err)
98
}
99
defer f.Close()
100
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", ns.Env, stdioFdCount+len(cmd.ExtraFiles)))
101
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
102
}
103
104
var cmdOut bytes.Buffer
105
var cmdErr bytes.Buffer
106
cmd.Stdout = &cmdOut
107
cmd.Stderr = &cmdErr // gpl: Why to we write the stderr to os.StdErr? Not sure, so keeping the behavior here...
108
cmd.Stdin = os.Stdin
109
err = cmd.Run()
110
_, _ = io.Copy(os.Stderr, &cmdErr)
111
log.FromBuffer(&cmdOut, log.WithFields(log.OWI("", "", instanceID)))
112
if err != nil {
113
// writing stderr to the error so clients can pattern match on specific errors
114
return fmt.Errorf("run nsinsider (%v) failed: %q \\ %q\n%v",
115
cmd.Args,
116
cmdOut.String(),
117
cmdErr.String(),
118
err,
119
)
120
}
121
return nil
122
}
123
124