1
2
3
4 package runc
5
6 import (
7 "encoding/json"
8 "os"
9 "path"
10 "path/filepath"
11 "strconv"
12 "strings"
13 "syscall"
14
15 oci "github.com/opencontainers/runtime-spec/specs-go"
16 "github.com/pkg/errors"
17 "golang.org/x/sys/unix"
18
19 "github.com/Microsoft/hcsshim/internal/guest/commonutils"
20 "github.com/Microsoft/hcsshim/internal/guest/runtime"
21 "github.com/Microsoft/hcsshim/internal/guest/stdio"
22 )
23
24 const (
25 containerFilesDir = "/var/run/gcsrunc"
26 initPidFilename = "initpid"
27 )
28
29 func setSubreaper(i int) error {
30 return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
31 }
32
33
34 func NewRuntime(logBasePath string) (runtime.Runtime, error) {
35 rtime := &runcRuntime{runcLogBasePath: logBasePath}
36 if err := rtime.initialize(); err != nil {
37 return nil, err
38 }
39 return rtime, nil
40 }
41
42
43
44 type runcRuntime struct {
45 runcLogBasePath string
46 }
47
48 var _ runtime.Runtime = &runcRuntime{}
49
50
51 func (r *runcRuntime) initialize() error {
52 paths := [2]string{containerFilesDir, r.runcLogBasePath}
53 for _, p := range paths {
54 _, err := os.Stat(p)
55 if err != nil {
56 if !os.IsNotExist(err) {
57 return err
58 }
59 if err := os.MkdirAll(p, 0700); err != nil {
60 return errors.Wrapf(err, "failed making runC container files directory %s", p)
61 }
62 }
63 }
64
65 return nil
66 }
67
68
69
70
71
72 func (r *runcRuntime) CreateContainer(id string, bundlePath string, stdioSet *stdio.ConnectionSet) (c runtime.Container, err error) {
73 c, err = r.runCreateCommand(id, bundlePath, stdioSet)
74 if err != nil {
75 return nil, err
76 }
77 return c, nil
78 }
79
80
81
82 func (*runcRuntime) ListContainerStates() ([]runtime.ContainerState, error) {
83 cmd := runcCommand("list", "-f", "json")
84 out, err := cmd.CombinedOutput()
85 if err != nil {
86 runcErr := parseRuncError(string(out))
87 return nil, errors.Wrapf(runcErr, "runc list failed with %v: %s", err, string(out))
88 }
89 var states []runtime.ContainerState
90 if err := json.Unmarshal(out, &states); err != nil {
91 return nil, errors.Wrap(err, "failed to unmarshal the states for the container list")
92 }
93 return states, nil
94 }
95
96
97
98 func (*runcRuntime) getRunningPids(id string) ([]int, error) {
99 cmd := runcCommand("ps", "-f", "json", id)
100 out, err := cmd.CombinedOutput()
101 if err != nil {
102 runcErr := parseRuncError(string(out))
103 return nil, errors.Wrapf(runcErr, "runc ps failed with %v: %s", err, string(out))
104 }
105 var pids []int
106 if err := json.Unmarshal(out, &pids); err != nil {
107 return nil, errors.Wrapf(err, "failed to unmarshal pids for container %s", id)
108 }
109 return pids, nil
110 }
111
112
113
114 func (*runcRuntime) getProcessCommand(pid int) ([]string, error) {
115
116
117 data, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
118 if err != nil {
119 return nil, errors.Wrapf(err, "failed to read cmdline file for process %d", pid)
120 }
121
122 cmdString := strings.TrimSuffix(string(data), "\x00")
123 return strings.Split(cmdString, "\x00"), nil
124 }
125
126
127
128 func (*runcRuntime) pidMapToProcessStates(pidMap map[int]*runtime.ContainerProcessState) []runtime.ContainerProcessState {
129 processStates := make([]runtime.ContainerProcessState, len(pidMap))
130 i := 0
131 for _, processState := range pidMap {
132 processStates[i] = *processState
133 i++
134 }
135 return processStates
136 }
137
138
139 func (r *runcRuntime) waitOnProcess(pid int) (int, error) {
140 process, err := os.FindProcess(pid)
141 if err != nil {
142 return -1, errors.Wrapf(err, "failed to find process %d", pid)
143 }
144 state, err := process.Wait()
145 if err != nil {
146 return -1, errors.Wrapf(err, "failed waiting on process %d", pid)
147 }
148
149 status := state.Sys().(syscall.WaitStatus)
150 if status.Signaled() {
151 return 128 + int(status.Signal()), nil
152 }
153 return status.ExitStatus(), nil
154 }
155
156
157 func (r *runcRuntime) runCreateCommand(id string, bundlePath string, stdioSet *stdio.ConnectionSet) (runtime.Container, error) {
158 c := &container{r: r, id: id}
159 if err := r.makeContainerDir(id); err != nil {
160 return nil, err
161 }
162
163 tempProcessDir, err := os.MkdirTemp(containerFilesDir, id)
164 if err != nil {
165 return nil, err
166 }
167
168 spec, err := ociSpecFromBundle(bundlePath)
169 if err != nil {
170 return nil, err
171 }
172
173
174
175
176
177
178
179
180
181 if spec.Linux != nil {
182 for _, ns := range spec.Linux.Namespaces {
183 if ns.Type == oci.PIDNamespace {
184 c.ownsPidNamespace = ns.Path == ""
185 }
186 }
187 }
188
189 if spec.Process.Cwd != "/" {
190 cwd := path.Join(bundlePath, "rootfs", spec.Process.Cwd)
191
192 _ = os.MkdirAll(cwd, 0755)
193 }
194
195 args := []string{"create", "-b", bundlePath, "--no-pivot"}
196
197 p, err := c.startProcess(tempProcessDir, spec.Process.Terminal, stdioSet, spec.Annotations, args...)
198 if err != nil {
199 return nil, err
200 }
201
202
203 containerDir := r.getContainerDir(id)
204 if err := os.WriteFile(filepath.Join(containerDir, initPidFilename), []byte(strconv.Itoa(p.pid)), 0777); err != nil {
205 return nil, err
206 }
207
208 c.init = p
209 return c, nil
210 }
211
212 func ociSpecFromBundle(bundlePath string) (*oci.Spec, error) {
213 configPath := filepath.Join(bundlePath, "config.json")
214 configFile, err := os.Open(configPath)
215 if err != nil {
216 return nil, errors.Wrapf(err, "failed to open bundle config at %s", configPath)
217 }
218 defer configFile.Close()
219 var spec *oci.Spec
220 if err := commonutils.DecodeJSONWithHresult(configFile, &spec); err != nil {
221 return nil, errors.Wrap(err, "failed to parse OCI spec")
222 }
223 return spec, nil
224 }
225
View as plain text