// SPDX-License-Identifier: GPL-3.0-or-later // Package system captures one cached, injectable view of live host state. package system import ( "os" "path/filepath" "sort" "strconv" "strings" "codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model" ) // Capture reads process state once from procRoot. Transient per-process read // failures degrade to empty fields, matching Python's fail-open Process view. func Capture(procRoot string) (model.State, error) { if procRoot == "" { procRoot = "/proc" } entries, err := os.ReadDir(procRoot) if err != nil { return model.State{}, err } processes := make([]model.Process, 0, len(entries)) for _, entry := range entries { pid, err := strconv.Atoi(entry.Name()) if err != nil || !entry.IsDir() { continue } base := filepath.Join(procRoot, entry.Name()) processes = append(processes, model.Process{ PID: pid, Comm: strings.TrimSpace(readText(filepath.Join(base, "comm"))), Cmdline: strings.TrimSpace(strings.ReplaceAll(readText(filepath.Join(base, "cmdline")), "\x00", " ")), Exe: readLink(filepath.Join(base, "exe")), }) } sort.Slice(processes, func(i, j int) bool { return processes[i].PID < processes[j].PID }) return model.State{Processes: processes}, nil } func readText(path string) string { raw, err := os.ReadFile(path) if err != nil { return "" } return string(raw) } func readLink(path string) string { target, err := os.Readlink(path) if err != nil { return "" } return target }