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 }