-
Notifications
You must be signed in to change notification settings - Fork 67
/
Copy pathenvironment_linux.go
229 lines (207 loc) · 5.58 KB
/
environment_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package linuxcontainer
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"github.com/criyle/go-judge/envexec"
"github.com/criyle/go-sandbox/container"
"github.com/criyle/go-sandbox/pkg/cgroup"
"github.com/criyle/go-sandbox/pkg/rlimit"
"github.com/criyle/go-sandbox/runner"
"golang.org/x/sys/unix"
)
var _ envexec.Environment = &environ{}
// environ defines interface to access container resources
type environ struct {
container.Environment
cgPool CgroupPool
wd *os.File // container work dir
workDir string
cpuset string
seccomp []syscall.SockFilter
cpuRate bool
}
// Destroy destroys the environment
func (c *environ) Destroy() error {
return c.Environment.Destroy()
}
func (c *environ) Reset() error {
return c.Environment.Reset()
}
// Execve execute process inside the environment
func (c *environ) Execve(ctx context.Context, param envexec.ExecveParam) (envexec.Process, error) {
var (
cg Cgroup
syncFunc func(int) error
err error
)
limit := param.Limit
if c.cgPool != nil {
cg, err = c.cgPool.Get()
if err != nil {
return nil, fmt.Errorf("execve: failed to get cgroup %v", err)
}
if err := c.setCgroupLimit(cg, limit); err != nil {
return nil, err
}
syncFunc = cg.AddProc
}
rLimits := rlimit.RLimits{
CPU: uint64(limit.Time.Truncate(time.Second)/time.Second) + 1,
FileSize: limit.Output.Byte(),
Stack: limit.Stack.Byte(),
OpenFile: limit.OpenFile,
DisableCore: true,
}
if limit.DataSegment || c.cgPool == nil {
rLimits.Data = limit.Memory.Byte()
}
if limit.AddressSpace {
rLimits.AddressSpace = limit.Memory.Byte()
}
// wait for sync or error before turn (avoid file close before pass to child process)
syncDone := make(chan struct{})
p := container.ExecveParam{
Args: param.Args,
Env: param.Env,
Files: param.Files,
CTTY: param.TTY,
ExecFile: param.ExecFile,
RLimits: rLimits.PrepareRLimit(),
Seccomp: c.seccomp,
SyncFunc: func(pid int) error {
defer close(syncDone)
if syncFunc != nil {
return syncFunc(pid)
}
return nil
},
}
proc := newProcess(func() runner.Result {
return c.Environment.Execve(ctx, p)
}, cg, c.cgPool)
select {
case <-proc.done:
case <-syncDone:
}
return proc, nil
}
// WorkDir returns opened work directory, should not close after
func (c *environ) WorkDir() *os.File {
c.wd.Seek(0, 0)
return c.wd
}
// Open opens file relative to work directory
func (c *environ) Open(path string, flags int, perm os.FileMode) (*os.File, error) {
if filepath.IsAbs(path) {
var err error
path, err = filepath.Rel(c.workDir, path)
if err != nil {
return nil, fmt.Errorf("openAtWorkDir: %v", err)
}
}
fd, err := syscall.Openat(int(c.wd.Fd()), path, flags|syscall.O_CLOEXEC, uint32(perm))
if err != nil {
return nil, &os.PathError{Op: "open", Path: path, Err: err}
}
f := os.NewFile(uintptr(fd), path)
if f == nil {
return nil, fmt.Errorf("openAtWorkDir: failed to NewFile")
}
return f, nil
}
// MkdirAll equivalent to os.MkdirAll but in container
func (c *environ) MkdirAll(path string, perm os.FileMode) error {
if path == "" || path == "." {
return nil
}
if filepath.IsAbs(path) {
r, err := filepath.Rel(c.workDir, path)
if err != nil {
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.EINVAL}
}
return c.MkdirAll(r, perm)
}
// fast path
wd := int(c.wd.Fd())
var stat unix.Stat_t
err := unix.Fstatat(wd, path, &stat, 0)
if err == nil {
if stat.Mode&syscall.S_IFMT == syscall.S_IFDIR {
return nil
}
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
}
// slow path
// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
i--
}
j := i
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
j--
}
if j > 1 {
// Create parent.
err = c.MkdirAll(path[:j-1], perm)
if err != nil {
return err
}
}
err = syscall.Mkdirat(wd, path, uint32(perm.Perm()))
if err != nil {
err1 := unix.Fstatat(wd, path, &stat, 0)
if err1 == nil && stat.Mode&syscall.S_IFMT == syscall.S_IFDIR {
return nil
}
return err
}
return nil
}
func (c *environ) Symlink(oldName, newName string) error {
var err error
if filepath.IsAbs(newName) {
newName, err = filepath.Rel(c.workDir, newName)
if err != nil {
return &os.PathError{Op: "symlink", Path: newName, Err: syscall.EINVAL}
}
}
if filepath.IsAbs(oldName) {
oldName, err = filepath.Rel(c.workDir, oldName)
if err != nil {
return &os.PathError{Op: "symlink", Path: oldName, Err: syscall.EINVAL}
}
}
return unix.Symlinkat(oldName, int(c.wd.Fd()), newName)
}
func (c *environ) setCgroupLimit(cg Cgroup, limit envexec.Limit) error {
cpuSet := limit.CPUSet
if cpuSet == "" {
cpuSet = c.cpuset
}
if cpuSet != "" {
if err := cg.SetCpuset(cpuSet); isCgroupSetHasError(err) {
return fmt.Errorf("execve: cgroup failed to set cpu_set limit %v", err)
}
}
if c.cpuRate && limit.Rate > 0 {
if err := cg.SetCPURate(limit.Rate); isCgroupSetHasError(err) {
return fmt.Errorf("execve: cgroup failed to set cpu_rate limit %v", err)
}
}
if err := cg.SetMemoryLimit(limit.Memory); isCgroupSetHasError(err) {
return fmt.Errorf("execve: cgroup failed to set memory limit %v", err)
}
if err := cg.SetProcLimit(limit.Proc); isCgroupSetHasError(err) {
return fmt.Errorf("execve: cgroup failed to set process limit %v", err)
}
return nil
}
func isCgroupSetHasError(err error) bool {
return err != nil && !errors.Is(err, cgroup.ErrNotInitialized) && !errors.Is(err, os.ErrNotExist)
}