~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
127 lines · 2.6 KB Raw History
//go:build linux

package devsup

import (
	"bytes"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
)

type procInfo struct {
	pid   int
	ppid  int
	pgid  int
	ticks uint64
	state byte
}

func readProcInfo(pid int) (procInfo, bool) {
	b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
	if err != nil {
		return procInfo{}, false
	}
	i := bytes.LastIndexByte(b, ')')
	if i < 0 {
		return procInfo{}, false
	}
	f := strings.Fields(string(b[i+1:]))
	if len(f) < 20 || len(f[0]) != 1 {
		return procInfo{}, false
	}
	ppid, _ := strconv.Atoi(f[1])
	pgid, _ := strconv.Atoi(f[2])
	ticks, _ := strconv.ParseUint(f[19], 10, 64)
	return procInfo{pid: pid, ppid: ppid, pgid: pgid, ticks: ticks, state: f[0][0]}, true
}

func ownPIDs() []int {
	entries, err := os.ReadDir("/proc")
	if err != nil {
		return nil
	}
	uid := uint32(os.Getuid())
	var out []int
	for _, e := range entries {
		pid, err := strconv.Atoi(e.Name())
		if err != nil {
			continue
		}
		fi, err := e.Info()
		if err != nil {
			continue
		}
		st, ok := fi.Sys().(*syscall.Stat_t)
		if !ok || st.Uid != uid {
			continue
		}
		out = append(out, pid)
	}
	return out
}

func procTable() []procInfo {
	var out []procInfo
	for _, pid := range ownPIDs() {
		if p, ok := readProcInfo(pid); ok && p.state != 'Z' && p.state != 'X' {
			out = append(out, p)
		}
	}
	return out
}

func procEnv(pid int, keys ...string) map[string]string {
	b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "environ"))
	if err != nil {
		return nil
	}
	out := map[string]string{}
	for kv := range bytes.SplitSeq(b, []byte{0}) {
		for _, k := range keys {
			if bytes.HasPrefix(kv, []byte(k+"=")) {
				out[k] = string(kv[len(k)+1:])
			}
		}
	}
	return out
}

func childrenOf(pid int) []int {
	tasks, err := os.ReadDir(filepath.Join("/proc", strconv.Itoa(pid), "task"))
	if err != nil {
		return nil
	}
	var out []int
	for _, t := range tasks {
		b, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "task", t.Name(), "children"))
		if err != nil {
			continue
		}
		for f := range strings.FieldsSeq(string(b)) {
			if n, err := strconv.Atoi(f); err == nil {
				out = append(out, n)
			}
		}
	}
	return out
}

type attribution struct {
	unit       string
	invocation string
}

func attributedTo(devsupID string) map[int]attribution {
	out := map[int]attribution{}
	for _, p := range procTable() {
		env := procEnv(p.pid, EnvDevsup, EnvUnit, EnvInvocation)
		if env[EnvDevsup] != devsupID || env[EnvUnit] == "" {
			continue
		}
		out[p.pid] = attribution{unit: env[EnvUnit], invocation: env[EnvInvocation]}
	}
	return out
}