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

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