gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/ps/ps_linux.go (about)

     1  // Copyright 2016-2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"reflect"
    14  	"strconv"
    15  	"strings"
    16  )
    17  
    18  const (
    19  	defaultGlob = "/proc"
    20  	userHZ      = 100
    21  )
    22  
    23  var (
    24  	psglob string
    25  	// by convention, the first element of the path is "/proc"
    26  	// This allows us to point to any place as our "/proc"
    27  	procdir = "/proc"
    28  )
    29  
    30  // Process contains both kernel-dependent and kernel-independent information.
    31  type Process struct {
    32  	process
    33  	status  string
    34  	cmdline string
    35  	stat    string
    36  	Pidno   int // process id #
    37  	uid     int
    38  }
    39  
    40  // table content of stat file defined by:
    41  // https://www.kernel.org/doc/Documentation/filesystems/proc.txt (2009)
    42  // Section (ctrl + f) : Table 1-4: Contents of the stat files (as of 2.6.30-rc7)
    43  type process struct {
    44  	Pid         string // process id name
    45  	Cmd         string // filename of the executable
    46  	State       string // state (R is running, S is sleeping, D is sleeping in an uninterruptible wait, Z is zombie, T is traced or stopped)
    47  	Ppid        string // process id of the parent process
    48  	Pgrp        string // pgrp of the process
    49  	Sid         string // session id
    50  	TTYNr       string // tty the process uses
    51  	TTYPgrp     string // pgrp of the tty
    52  	Flags       string // task flags
    53  	MinFlt      string // number of minor faults
    54  	CminFlt     string // number of minor faults with child's
    55  	MajFlt      string // number of major faults
    56  	CmajFlt     string // number of major faults with child's
    57  	Utime       string // user mode jiffies
    58  	Stime       string // kernel mode jiffies
    59  	Cutime      string // user mode jiffies with child's
    60  	Cstime      string // kernel mode jiffies with child's
    61  	Priority    string // priority level
    62  	Nice        string // nice level
    63  	NumThreads  string // number of threads
    64  	ItRealValue string // (obsolete, always 0)
    65  	StartTime   string // time the process started after system boot
    66  	Vsize       string // virtual memory size
    67  	Rss         string // resident set memory size
    68  	Rsslim      string // current limit in bytes on the rss
    69  	StartCode   string // address above which program text can run
    70  	EndCode     string // address below which program text can run
    71  	StartStack  string // address of the start of the main process stack
    72  	Esp         string // current value of ESP
    73  	Eip         string // current value of EIP
    74  	Pending     string // bitmap of pending signals
    75  	Blocked     string // bitmap of blocked signals
    76  	Sigign      string // bitmap of ignored signals
    77  	Sigcatch    string // bitmap of caught signals
    78  	Wchan       string // place holder, used to be the wchan address, use /proc/PID/wchan
    79  	Zero1       string // ignored
    80  	Zero2       string // ignored
    81  	ExitSignal  string // signal to send to parent thread on exit
    82  	TaskCPU     string // which CPU the task is scheduled on
    83  	RtPriority  string // realtime priority
    84  	Policy      string // scheduling policy (man sched_setscheduler)
    85  	BlkioTicks  string // time spent waiting for block IO
    86  	Gtime       string // guest time of the task in jiffies
    87  	Cgtime      string // guest time of the task children in jiffies
    88  	StartData   string // address above which program data+bss is placed
    89  	EndData     string // address below which program data+bss is placed
    90  	StartBrk    string // address above which program heap can be expanded with brk()
    91  	ArgStart    string // address above which program command line is placed
    92  	ArgEnd      string // address below which program command line is placed
    93  	EnvStart    string // address above which program environment is placed
    94  	EnvEnd      string // address below which program environment is placed
    95  	ExitCode    string // the thread's exit_code in the form reported by the waitpid system call (end of stat)
    96  	Ctty        string // extra member (don't parsed from stat)
    97  	Time        string // extra member (don't parsed from stat)
    98  }
    99  
   100  // Parse all content of stat to a Process Struct
   101  // by gived the pid (linux)
   102  func (p *Process) readStat(s string) error {
   103  	fields := strings.Split(s, " ")
   104  	// set struct fields from stat file data
   105  	v := reflect.ValueOf(&p.process).Elem()
   106  	for i := 0; i < len(fields); i++ {
   107  		fieldVal := v.Field(i)
   108  		fieldVal.Set(reflect.ValueOf(fields[i]))
   109  	}
   110  
   111  	p.Time = p.getTime()
   112  	p.Ctty = p.getCtty()
   113  	p.Cmd = strings.TrimSuffix(strings.TrimPrefix(p.Cmd, "("), ")")
   114  	if flags.x && p.cmdline != "" {
   115  		p.Cmd = p.cmdline
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Parse data from various strings in the Process struct
   122  func (p *Process) Parse() error {
   123  	err := p.readStat(p.stat)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	if p.uid, err = p.GetUID(); err != nil {
   128  		return err
   129  	}
   130  	return nil
   131  }
   132  
   133  // ctty returns the ctty or "?" if none can be found.
   134  // TODO: an right way to get ctty by p.TTYNr and p.TTYPgrp
   135  func (p process) getCtty() string {
   136  	if tty, err := os.Readlink(filepath.Join(procdir, p.Pid, "fd/0")); err != nil {
   137  		return "?"
   138  	} else if p.TTYPgrp != "-1" {
   139  		if len(tty) > 5 && tty[:5] == "/dev/" {
   140  			tty = tty[5:]
   141  		}
   142  		return tty
   143  	}
   144  	return "?"
   145  }
   146  
   147  // Get a named field of stat type
   148  // e.g.: p.getField("Pid") => '1'
   149  func (p *process) getField(field string) string {
   150  	v := reflect.ValueOf(p).Elem()
   151  	return fmt.Sprintf("%v", v.FieldByName(field))
   152  }
   153  
   154  // Search for attributes about the process
   155  func (p *Process) Search(field string) string {
   156  	return p.process.getField(field)
   157  }
   158  
   159  // GetUID gets the UID of the process from the status string
   160  func (p Process) GetUID() (int, error) {
   161  	lines := strings.Split(p.status, "\n")
   162  	for _, line := range lines {
   163  		if strings.Contains(line, "Uid") {
   164  			fields := strings.Split(line, "\t")
   165  			return strconv.Atoi(fields[1])
   166  		}
   167  	}
   168  
   169  	return -1, fmt.Errorf("no Uid string in %s", p.status)
   170  
   171  }
   172  
   173  // Get total time stat formated hh:mm:ss
   174  func (p process) getTime() string {
   175  	utime, _ := strconv.Atoi(p.Utime)
   176  	stime, _ := strconv.Atoi(p.Stime)
   177  	jiffies := utime + stime
   178  
   179  	tsecs := jiffies / userHZ
   180  	secs := tsecs % 60
   181  	mins := (tsecs / 60) % 60
   182  	hrs := tsecs / 3600
   183  
   184  	return fmt.Sprintf("%02d:%02d:%02d", hrs, mins, secs)
   185  }
   186  
   187  func getAllGlobNames() []string {
   188  	psglob = os.Getenv("UROOT_PSPATH")
   189  	if psglob == "" {
   190  		// The reason we glob with stat, even though
   191  		// we strip it off later, is it is a cheap way
   192  		// to ensure we're getting a process directory
   193  		// and not some other weird thing in /proc.
   194  		psglob = defaultGlob
   195  	}
   196  	l := filepath.SplitList(psglob)
   197  	if len(l) > 0 {
   198  		procdir = l[0]
   199  	}
   200  	return l
   201  }
   202  
   203  // Create a set of stat file names from an array of globs
   204  func getAllStatNames(globs []string) ([]string, error) {
   205  	var list []string
   206  	for _, g := range globs {
   207  		l, err := filepath.Glob(filepath.Join(g, "[0-9]*/stat"))
   208  		if err != nil {
   209  			log.Printf("Glob err on %s: %v", g, err)
   210  			continue
   211  		}
   212  		list = append(list, l...)
   213  	}
   214  	if len(list) == 0 {
   215  		return nil, fmt.Errorf("no files found in %q; check if proc is mounted", psglob)
   216  	}
   217  	return list, nil
   218  }
   219  
   220  func file(s string) (string, error) {
   221  	b, err := ioutil.ReadFile(s)
   222  	return string(b), err
   223  }
   224  
   225  func (pT *ProcessTable) doTable(statFileNames []string) error {
   226  	var err error
   227  	for _, stat := range statFileNames {
   228  		p := &Process{}
   229  
   230  		//log.Printf("Check %s", stat)
   231  		// ps is a snapshot in time of /proc. Hence we want to grab
   232  		// all the files we need in as close to an instant in time as
   233  		// we can.
   234  		// Read the files. It may have vanished or we may not have
   235  		// access; we do not consider those to be errors.
   236  		// if *any* of the files are not there, just skip this pid.
   237  		p.stat, err = file(stat)
   238  		if err != nil {
   239  			continue
   240  		}
   241  		d := filepath.Dir(stat)
   242  		pid := filepath.Base(d)
   243  		pidno, err := strconv.Atoi(pid)
   244  		if err != nil {
   245  			return fmt.Errorf("last element of %v is not a number", pid)
   246  		}
   247  		p.status, err = file(filepath.Join(d, "status"))
   248  		if err != nil {
   249  			continue
   250  		}
   251  		if flags.x {
   252  			p.cmdline, err = file(filepath.Join(d, "cmdline"))
   253  			if err != nil {
   254  				continue
   255  			}
   256  		}
   257  		// if filepath.Base is *not* proc, then use it, else
   258  		// it's just the directory containing the pid.
   259  		proot := filepath.Dir(d)
   260  		//log.Printf("procdir %v d %v proot %v", procdir, d, proot)
   261  		if proot != procdir {
   262  			pid = filepath.Join(filepath.Base(proot), pid)
   263  		}
   264  		p.Pidno = pidno
   265  		if err := p.Parse(); err != nil {
   266  			return err
   267  		}
   268  		p.Pid = pid
   269  		//log.Printf("stat is %v p is %v", stat,p)
   270  		if p.Pidno == os.Getpid() {
   271  			pT.mProc = p
   272  		}
   273  		pT.table = append(pT.table, p)
   274  	}
   275  	// if mProc is nil, something is really wrong.
   276  	if pT.mProc == nil && len(pT.table) > 0 {
   277  		pT.mProc = pT.table[0]
   278  	}
   279  	return nil
   280  }
   281  
   282  // LoadTable creates a ProcessTable containing stats on all processes.
   283  // We use UROOT_PSPATH if set, else the default glob
   284  // of /proc/[0-9]*/stat.
   285  // We want to allow ps to run against the standard /proc but also
   286  // proc mounted over a network in, e.g., /netproc/host/pid/...
   287  // (i.e. we mount node:/proc on /netproc/node)
   288  // The question then becomes what to store for the pid.
   289  // For /proc, it's easy: strip the first directory component.
   290  // For additional directories, e.g. /netproc/host/[0-9]*/stat,
   291  // we can follow the same rule: strip the first component.
   292  // We will do that for now and see if it works; if not we'll
   293  // need more complex processing for UROOT_PSPATH.
   294  func (pT *ProcessTable) LoadTable() error {
   295  	g := getAllGlobNames()
   296  	n, err := getAllStatNames(g)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	return pT.doTable(n)
   301  }