...

Text file src/golang.conradwood.net/go-easyops/linux/com.go~

Documentation: golang.conradwood.net/go-easyops/linux

     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