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