...
1package main
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "os"
8 "os/exec"
9 "os/user"
10 "strconv"
11 "sync"
12 "syscall"
13
14 "golang.conradwood.net/go-easyops/errors"
15 "golang.conradwood.net/go-easyops/utils"
16 "golang.org/x/sys/unix"
17)
18
19/*
20 CGROUP Permissions:
21
22 clone3 returns -EACCESS (permission denied) unless:
23 the user has write access to cgroup.procs in the nearest common ancestor director of calling process and cgroup of new process.
24
25 EXAMPLES: (assuming only LINUXCOM and below is user-writeable)
26 | CALLING_PROC | NEW_PROC | Result |
27 +---------------------------------+------------------------------------+----------+
28 | /sys/fs/cgroup/LINUXCOM/me/ | /sys/fs/cgroup/LINUXCOM/com_1/ | EACCESS |
29 | /sys/fs/cgroup/LINUXCOM/foo/me/ | /sys/fs/cgroup/LINUXCOM/com_1/ | EACCESS |
30 | /sys/fs/cgroup/LINUXCOM/foo/me/ | /sys/fs/cgroup/LINUXCOM/foo/com_1/ | OK |
31*/
32
33var (
34 ctr = 0
35 ctrlock sync.Mutex
36)
37
38type Command interface {
39}
40type command struct {
41 cgroupdir string
42 stdinwriter io.Writer
43 stdoutreader io.Reader
44 stderrreader io.Reader
45 instance *cominstance
46}
47type cominstance struct {
48 exe []string
49 command *command
50 cgroupdir_cmd string
51 com *exec.Cmd
52 stdout_pipe io.ReadCloser
53 stderr_pipe io.ReadCloser
54 defStdoutReader *comDefaultReader
55 defStderrReader *comDefaultReader
56}
57
58func NewCommand() *command {
59 return &command{
60 cgroupdir: "/sys/fs/cgroup/LINUXCOM/ancestor/",
61 }
62}
63
64// e.g. /sys/fs/cgroup/LINUXCOM
65func (c *command) SetCGroupDir(dir string) {
66 c.cgroupdir = dir
67}
68
69func (c *command) StdinWriter(r io.Writer) {
70 c.stdinwriter = r
71}
72func (c *command) StdoutReader(r io.Reader) {
73 c.stdoutreader = r
74}
75func (c *command) StderrReader(r io.Reader) {
76 c.stderrreader = r
77}
78func (c *command) IsRunning() bool {
79 return true
80}
81
82// failed to start, then error
83func (c *command) Start(ctx context.Context, com ...string) (*cominstance, error) {
84 n := newctr()
85 cgroupdir_cmd := fmt.Sprintf("%s/com_%d", c.cgroupdir, n)
86 err := mkdir(cgroupdir_cmd + "/tasks")
87 if err != nil {
88 return nil, err
89 }
90 ci := &cominstance{command: c, cgroupdir_cmd: cgroupdir_cmd}
91 c.instance = ci
92 return ci, ci.start(ctx, com...)
93}
94func (ci *cominstance) start(ctx context.Context, com ...string) error {
95 u, err := user.Current()
96 if err != nil {
97 return errors.Wrap(err)
98 }
99
100 uid, err := strconv.Atoi(u.Uid)
101 if err != nil {
102 return errors.Wrap(err)
103 }
104
105 gid, err := strconv.Atoi(u.Gid)
106 if err != nil {
107 return errors.Wrap(err)
108 }
109
110 // open cgroup filedescriptor
111 cgroup_fd_path := ci.cgroupdir_cmd
112 cgroup_fd, err := syscall.Open(cgroup_fd_path, unix.O_PATH, 0)
113 if err != nil {
114 return errors.Wrap(err)
115 }
116 fmt.Printf("CgroupFD for \"%s\": %d\n", cgroup_fd_path, cgroup_fd)
117 ci.com = exec.CommandContext(ctx, com[0], com[1:]...)
118 ci.stdout_pipe, err = ci.com.StdoutPipe()
119 if err != nil {
120 return err
121 }
122 ci.defStdoutReader = newDefaultReader(ci.stdout_pipe)
123 ci.stderr_pipe, err = ci.com.StderrPipe()
124 if err != nil {
125 return err
126 }
127 ci.defStderrReader = newDefaultReader(ci.stdout_pipe)
128
129 ci.com.SysProcAttr = &syscall.SysProcAttr{
130 Credential: &syscall.Credential{
131 Uid: uint32(uid),
132 Gid: uint32(gid),
133 NoSetGroups: true,
134 },
135 UseCgroupFD: true,
136 CgroupFD: cgroup_fd,
137 }
138 err = ci.com.Start()
139 if err != nil {
140 return errors.Wrap(err)
141 }
142 return nil
143}
144
145func (ci *cominstance) Wait(ctx context.Context) error {
146 if ci.com == nil {
147 return nil
148 }
149 err := ci.com.Wait()
150 return err
151}
152func (c *command) ExitCode() int {
153 return 0
154}
155func (c *command) CombinedOutput() []byte {
156 return nil
157}
158func (c *command) SigInt() error { // -2
159 fmt.Printf("sending sigint\n")
160 return c.sendsig(syscall.SIGINT)
161}
162func (c *command) SigKill() error { // -9
163 fmt.Printf("sending sigkill\n")
164 return c.sendsig(syscall.SIGKILL)
165}
166
167func (c *command) sendsig(sig syscall.Signal) error {
168 ci := c.instance
169 pids, err := get_pids_for_cgroup(ci.cgroupdir_cmd)
170 if err != nil {
171 fmt.Printf("Could not get pids for cgroup \"%s\": %s\n", ci.cgroupdir_cmd, err)
172 return err
173 }
174 fmt.Printf("Cgroupdir \"%s\" has %d pids\n", ci.cgroupdir_cmd, len(pids))
175 for _, pid := range pids {
176 fmt.Printf("Sending signal %v to pid %d\n", sig, pid)
177 err = syscall.Kill(int(pid), sig)
178 if err != nil {
179 return errors.Wrap(err)
180 }
181 }
182 return nil
183}
184
185func newctr() int {
186 ctrlock.Lock()
187 ctr++
188 res := ctr
189 ctrlock.Unlock()
190 return res
191}
192
193func mkdir(dir string) error {
194 if utils.FileExists(dir) {
195 return nil
196 }
197 err := os.MkdirAll(dir, 0777)
198 if err != nil {
199 return errors.Wrap(err)
200 }
201 if !utils.FileExists(dir) {
202 return errors.Errorf("failed to create \"%s\"", dir)
203 }
204 return nil
205}
View as plain text