pkg.re/essentialkaos/ek@v12.36.0+incompatible/system/process/processes_linux.go (about)

     1  // +build linux
     2  
     3  // Package process provides methods for gathering information about active processes
     4  package process
     5  
     6  // ////////////////////////////////////////////////////////////////////////////////// //
     7  //                                                                                    //
     8  //                         Copyright (c) 2021 ESSENTIAL KAOS                          //
     9  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
    10  //                                                                                    //
    11  // ////////////////////////////////////////////////////////////////////////////////// //
    12  
    13  import (
    14  	"fmt"
    15  	"io/ioutil"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"pkg.re/essentialkaos/ek.v12/fsutil"
    20  	"pkg.re/essentialkaos/ek.v12/system"
    21  )
    22  
    23  // ////////////////////////////////////////////////////////////////////////////////// //
    24  
    25  // ProcessInfo contains basic info about process
    26  type ProcessInfo struct {
    27  	Command  string         // Full command
    28  	User     string         // Username
    29  	PID      int            // PID
    30  	Parent   int            // Parent process PID
    31  	Childs   []*ProcessInfo // Slice with child processes
    32  	IsThread bool           // True if process is thread
    33  }
    34  
    35  // ////////////////////////////////////////////////////////////////////////////////// //
    36  
    37  // procFS is path to procfs
    38  var procFS = "/proc"
    39  
    40  // ////////////////////////////////////////////////////////////////////////////////// //
    41  
    42  // GetTree returns root process with all subprocesses on the system
    43  func GetTree(pid ...int) (*ProcessInfo, error) {
    44  	root := 1
    45  
    46  	if len(pid) != 0 {
    47  		root = pid[0]
    48  	}
    49  
    50  	if !fsutil.IsExist(procFS + "/" + strconv.Itoa(root)) {
    51  		return nil, fmt.Errorf("Process with PID %d doesn't exist", pid)
    52  	}
    53  
    54  	list, err := findInfo(procFS, make(map[int]string))
    55  
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	if len(list) == 0 {
    61  		return nil, fmt.Errorf("Can't find any processes")
    62  	}
    63  
    64  	return processListToTree(list, root), nil
    65  }
    66  
    67  // GetList returns slice with all active processes on the system
    68  func GetList() ([]*ProcessInfo, error) {
    69  	return findInfo(procFS, make(map[int]string))
    70  }
    71  
    72  // ////////////////////////////////////////////////////////////////////////////////// //
    73  
    74  func findInfo(dir string, userMap map[int]string) ([]*ProcessInfo, error) {
    75  	var result []*ProcessInfo
    76  
    77  	dirs := fsutil.List(dir, true, fsutil.ListingFilter{Perms: "DRX"})
    78  
    79  	for _, pid := range dirs {
    80  		if !isPID(pid) {
    81  			continue
    82  		}
    83  
    84  		taskDir := dir + "/" + pid + "/task"
    85  
    86  		if fsutil.IsExist(taskDir) {
    87  			threads, err := findInfo(taskDir, userMap)
    88  
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  
    93  			if len(threads) == 0 {
    94  				continue
    95  			}
    96  
    97  			result = append(result, threads...)
    98  
    99  			continue
   100  		}
   101  
   102  		info, err := readProcessInfo(dir+"/"+pid, pid, userMap)
   103  
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  
   108  		if info == nil {
   109  			continue
   110  		}
   111  
   112  		result = append(result, info)
   113  	}
   114  
   115  	return result, nil
   116  }
   117  
   118  func readProcessInfo(dir, pid string, userMap map[int]string) (*ProcessInfo, error) {
   119  	pidInt, err := strconv.Atoi(pid)
   120  
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	// The process had died after the moment when we have created a list of processes
   126  	if !fsutil.IsExist(dir + "/cmdline") {
   127  		return nil, nil
   128  	}
   129  
   130  	cmd, err := ioutil.ReadFile(dir + "/cmdline")
   131  
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	if len(cmd) == 0 {
   137  		return nil, nil
   138  	}
   139  
   140  	uid, _, err := fsutil.GetOwner(dir)
   141  
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	username, err := getProcessUser(uid, userMap)
   147  
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	ppid, isThread := getProcessParent(dir, pidInt)
   153  
   154  	return &ProcessInfo{
   155  		Command:  formatCommand(string(cmd)),
   156  		User:     username,
   157  		PID:      pidInt,
   158  		Parent:   ppid,
   159  		IsThread: isThread,
   160  	}, nil
   161  }
   162  
   163  func getProcessUser(uid int, userMap map[int]string) (string, error) {
   164  	if uid == 0 {
   165  		return "root", nil
   166  	}
   167  
   168  	if userMap[uid] != "" {
   169  		return userMap[uid], nil
   170  	}
   171  
   172  	user, err := system.LookupUser(strconv.Itoa(uid))
   173  
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  
   178  	userMap[uid] = user.Name
   179  
   180  	return user.Name, nil
   181  }
   182  
   183  func getProcessParent(pidDir string, pid int) (int, bool) {
   184  	tgid, ppid := getParentPIDs(pidDir)
   185  
   186  	if tgid != pid {
   187  		return tgid, true
   188  	}
   189  
   190  	return ppid, false
   191  }
   192  
   193  func getParentPIDs(pidDir string) (int, int) {
   194  	data, err := ioutil.ReadFile(pidDir + "/status")
   195  
   196  	if err != nil {
   197  		return -1, -1
   198  	}
   199  
   200  	var ppid, tgid string
   201  
   202  	for _, line := range strings.Split(string(data), "\n") {
   203  		if strings.HasPrefix(line, "Tgid:") {
   204  			tgid = strings.TrimSpace(line[5:])
   205  		}
   206  
   207  		if strings.HasPrefix(line, "PPid:") {
   208  			ppid = strings.TrimSpace(line[5:])
   209  		}
   210  
   211  		if ppid != "" && tgid != "" {
   212  			break
   213  		}
   214  	}
   215  
   216  	if tgid == "" || ppid == "" {
   217  		return -1, -1
   218  	}
   219  
   220  	tgidInt, tgidErr := strconv.Atoi(tgid)
   221  	ppidInt, ppidErr := strconv.Atoi(ppid)
   222  
   223  	if tgidErr != nil || ppidErr != nil {
   224  		return -1, -1
   225  	}
   226  
   227  	return tgidInt, ppidInt
   228  }
   229  
   230  func formatCommand(cmd string) string {
   231  	// Normalize delimiters
   232  	command := strings.Replace(cmd, "\000", " ", -1)
   233  
   234  	// Remove space on the end of command
   235  	command = strings.TrimSpace(command)
   236  
   237  	return command
   238  }
   239  
   240  func processListToTree(processes []*ProcessInfo, root int) *ProcessInfo {
   241  	var result = make(map[int]*ProcessInfo)
   242  
   243  	for _, info := range processes {
   244  		result[info.PID] = info
   245  	}
   246  
   247  	for _, process := range result {
   248  		if process.Parent < 0 {
   249  			continue
   250  		}
   251  
   252  		parentProcess := result[process.Parent]
   253  
   254  		if parentProcess == nil {
   255  			continue
   256  		}
   257  
   258  		parentProcess.Childs = append(parentProcess.Childs, process)
   259  	}
   260  
   261  	return result[root]
   262  }
   263  
   264  func isPID(pid string) bool {
   265  	if pid == "" {
   266  		return false
   267  	}
   268  
   269  	// Pid must start from number
   270  	switch pid[0] {
   271  	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   272  		return true
   273  	}
   274  
   275  	return false
   276  }