package testsuite import ( "bytes" "errors" "fmt" "os" "path/filepath" "strconv" "strings" "syscall" "unsafe" "hakurei.app/fhs" ) // Stat represents status information read from /proc/pid/stat. type Stat struct { // The process ID. PID int // The filename of the executable, with parenthesis stripped. Comm string // One of the following characters, indicating process state: // // R Running // // S Sleeping in an interruptible wait // // D Waiting in uninterruptible disk sleep // // Z Zombie // // T Stopped (on a signal) or (before Linux // 2.6.33) trace stopped // // t Tracing stop (Linux 2.6.33 onward) // // W Paging (only before Linux 2.6.0) // // X Dead (from Linux 2.6.0 onward) // // x Dead (Linux 2.6.33 to 3.13 only) // // K Wakekill (Linux 2.6.33 to 3.13 only) // // W Waking (Linux 2.6.33 to 3.13 only) // // P Parked (Linux 3.9 to 3.13 only) // // I Idle (Linux 4.14 onward) State byte // The process ID of the parent of this process. PPID int // The process group ID of the process. PGRP int // The session ID of the process. Session int // The controlling terminal of the process. TTYNR int // The ID of the foreground process group of the controlling terminal of the // process. TPGID int // The kernel flags word of the process. For bit meanings, see the PF_* // defines in the Linux kernel source file include/linux/sched.h. Flags uint // The number of minor faults the process has made which have not required // loading a memory page from disk. MinFlt uint // The number of minor faults that the process's waited-for children have // made. CMinFlt uint // The number of major faults the process has made which have required // loading a memory page from disk. MajFlt uint // The number of major faults that the process's waited-for children have // made. CMajFlt uint // Amount of time that this process has been scheduled in user mode, // measured in clock ticks. UTime uint // Amount of time that this process has been scheduled in kernel mode, // measured in clock ticks. STime uint // Amount of time that this process's waited-for children have been // scheduled in user mode, measured in clock ticks. CUTime int // Amount of time that this process's waited-for children have been // scheduled in kernel mode, measured in clock ticks. CSTime int // For processes running a real-time scheduling policy, this is the negated // scheduling priority, minus one. Priority int // The nice value, a value in the range 19 (low priority) to -20 (high // priority). Nice int // Number of threads in this process. NumThreads int // unmaintained field: itrealvalue // The time the process started after system boot. Since Linux 2.6, the // value is expressed in clock ticks. StartTime uint64 // Virtual memory size in bytes. VSize uint // Resident set size in pages. RSS int // Soft limit in bytes on the rss of the process. RSSLim uint64 // The address above which program text can run. StartCode uint64 // The address below which program text can run. EndCode uint64 // The address of the start (i.e., bottom) of the stack. StartStack uint64 // The current value of ESP (stack pointer), as found in the kernel stack // page for the process. KSTKESP uint64 // The current EIP (instruction pointer). KSTKEIP uint64 // obsolete fields: signal, blocked, sigignore, sigcatch // This is the "channel" in which the process is waiting. It is the address // of a location in the kernel where the process is sleeping. WChan uint64 // unmaintained fields: nswap, cnswap // Signal to be sent to parent when we die. ExitSignal int // CPU number last executed on. Processor int // Real-time scheduling priority, a number in the range 1 to 99 for processes // scheduled under a real-time policy, or 0, for non-real-time processes. RTPriority uint // Scheduling policy (see sched_setscheduler(2)). Decode using the SCHED_* // constants in linux/sched.h. Policy uint // Aggregated block I/O delays, measured in clock ticks (centiseconds). DelayAcctBlkIOTicks uint64 // Guest time of the process (time spent running a virtual CPU for a guest // operating system), measured in clock ticks. GuestTime int // Guest time of the process's children, measured in clock ticks. CGuestTime int } // Executable is like [os.Executable], but for the process referred to by s. func (s *Stat) Executable() (string, error) { path, err := os.Readlink(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "exe")) // When the executable has been deleted then Readlink returns a // path appended with " (deleted)". return strings.TrimSuffix(path, " (deleted)"), err } // Stat populates stat with the proc filesystem entry referred to by s. func (s *Stat) Stat(stat *syscall.Stat_t) (err error) { err = syscall.Stat(filepath.Join(fhs.Proc, strconv.Itoa(s.PID)), stat) if err != nil { err = os.NewSyscallError("stat", err) } return } // Args reads arguments of the process referred to by s. func (s *Stat) Args() ([]string, error) { p, err := os.ReadFile(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "cmdline")) if err != nil { return nil, err } a := bytes.Split(p, []byte{0}) if len(a) > 0 && len(a[len(a)-1]) == 0 { a = a[:len(a)-1] } args := make([]string, len(a)) for i, arg := range a { args[i] = unsafe.String(unsafe.SliceData(arg), len(arg)) } return args, nil } // ErrBadDelimiters is returned by [Stat.UnmarshalText] if one or both bytes of // the comm delimiter pair were missing or misplaced. var ErrBadDelimiters = errors.New("missing comm delimiters") // UnmarshalText populates the structure pointed to by s from text. func (s *Stat) UnmarshalText(text []byte) (err error) { var ( discard uint64 _uint64 = &discard _int64 = (*int64)(unsafe.Pointer(&discard)) ld = bytes.Index(text, []byte("(")) rd = bytes.LastIndex(text, []byte(")")) ) if ld <= 0 || rd < 0 { return ErrBadDelimiters } if s.PID, err = strconv.Atoi( unsafe.String(unsafe.SliceData(text), ld-1), ); err != nil { return } s.Comm = string(text[ld+1 : rd]) var ( n int state string ) n, err = fmt.Fscan( bytes.NewBuffer(text[rd+2:]), &state, &s.PPID, &s.PGRP, &s.Session, &s.TTYNR, &s.TPGID, &s.Flags, &s.MinFlt, &s.CMinFlt, &s.MajFlt, &s.CMajFlt, &s.UTime, &s.STime, &s.CUTime, &s.CSTime, &s.Priority, &s.Nice, &s.NumThreads, _int64, &s.StartTime, &s.VSize, &s.RSS, &s.RSSLim, &s.StartCode, &s.EndCode, &s.StartStack, &s.KSTKESP, &s.KSTKEIP, _uint64, _uint64, _uint64, _uint64, &s.WChan, _uint64, _uint64, &s.ExitSignal, &s.Processor, &s.RTPriority, &s.Policy, &s.DelayAcctBlkIOTicks, &s.GuestTime, &s.CGuestTime, ) if err != nil { err = fmt.Errorf("field %d: %w", n, err) } else if len(state) != 1 { err = fmt.Errorf("invalid state %q", state) } else { s.State = state[0] } return } // A StatScanner continuously scans the proc filesystem for process status // information in /proc/pid/stat. type StatScanner struct { // Current entry. stat Stat // Cached top-level /proc entries. dents []os.DirEntry // Current progress through dents. i int // Whether the previous call to Scan had repopulated dents. wrapped bool // First stored error: a non-nil err disables the scanner. err error } // IsNotExist returns whether an error is [os.ErrNotExist] or ESRCH. func IsNotExist(err error) bool { return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ESRCH) } // Scan reads a process status information entry. It returns false if an // unrecoverable error is encountered, after which Scan no longer scans new // entries. func (s *StatScanner) Scan() bool { if s.err != nil { return false } if s.wrapped = s.i == len(s.dents); s.wrapped { if s.dents, s.err = os.ReadDir(fhs.Proc); s.err != nil { return false } s.i = 0 if len(s.dents) == 0 { s.err = syscall.ENOTRECOVERABLE return false } } for s.i < len(s.dents) { dent := s.dents[s.i] s.i++ if !dent.IsDir() { continue } pid, err := strconv.Atoi(dent.Name()) if err != nil { continue } var p []byte p, err = os.ReadFile(filepath.Join(fhs.Proc, dent.Name(), "stat")) if err != nil { if IsNotExist(err) { continue } s.err = err return false } s.err = s.stat.UnmarshalText(p) if s.err == nil && pid != s.stat.PID { s.err = fmt.Errorf( "bad status information: dent=%d, stat=%d", pid, s.stat.PID, ) } return s.err == nil } return s.Scan() } // Stat returns the address of the [Stat] structure populated by the last call // to Scan. func (s *StatScanner) Stat() *Stat { return &s.stat } // Err returns the stored error value. func (s *StatScanner) Err() error { return s.err } // Repopulated returns whether the last Scan call had re-read the proc filesystem. func (s *StatScanner) Repopulated() bool { return s.wrapped }