1 /*
2 htop - OpenBSDProcessList.c
3 (C) 2014 Hisham H. Muhammad
4 (C) 2015 Michael McConville
5 Released under the GNU GPLv2+, see the COPYING file
6 in the source distribution for its full text.
7 */
8 
9 #include "openbsd/OpenBSDProcessList.h"
10 
11 #include <kvm.h>
12 #include <limits.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <sys/mount.h>
17 #include <sys/param.h>
18 #include <sys/proc.h>
19 #include <sys/sched.h>
20 #include <sys/swap.h>
21 #include <sys/sysctl.h>
22 #include <sys/types.h>
23 #include <uvm/uvmexp.h>
24 
25 #include "CRT.h"
26 #include "Macros.h"
27 #include "Object.h"
28 #include "Process.h"
29 #include "ProcessList.h"
30 #include "Settings.h"
31 #include "XUtils.h"
32 #include "openbsd/OpenBSDProcess.h"
33 
34 
35 static long fscale;
36 static int pageSize;
37 static int pageSizeKB;
38 
OpenBSDProcessList_updateCPUcount(ProcessList * super)39 static void OpenBSDProcessList_updateCPUcount(ProcessList* super) {
40    OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
41    const int nmib[] = { CTL_HW, HW_NCPU };
42    const int mib[] = { CTL_HW, HW_NCPUONLINE };
43    int r;
44    unsigned int value;
45    size_t size;
46    bool change = false;
47 
48    size = sizeof(value);
49    r = sysctl(mib, 2, &value, &size, NULL, 0);
50    if (r < 0 || value < 1) {
51       value = 1;
52    }
53 
54    if (value != super->activeCPUs) {
55       super->activeCPUs = value;
56       change = true;
57    }
58 
59    size = sizeof(value);
60    r = sysctl(nmib, 2, &value, &size, NULL, 0);
61    if (r < 0 || value < 1) {
62       value = super->activeCPUs;
63    }
64 
65    if (value != super->existingCPUs) {
66       opl->cpuData = xReallocArray(opl->cpuData, value + 1, sizeof(CPUData));
67       super->existingCPUs = value;
68       change = true;
69    }
70 
71    if (change) {
72       CPUData* dAvg = &opl->cpuData[0];
73       memset(dAvg, '\0', sizeof(CPUData));
74       dAvg->totalTime = 1;
75       dAvg->totalPeriod = 1;
76       dAvg->online = true;
77 
78       for (unsigned int i = 0; i < super->existingCPUs; i++) {
79          CPUData* d = &opl->cpuData[i + 1];
80          memset(d, '\0', sizeof(CPUData));
81          d->totalTime = 1;
82          d->totalPeriod = 1;
83 
84          const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i };
85          struct cpustats cpu_stats;
86 
87          size = sizeof(cpu_stats);
88          if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) {
89             CRT_fatalError("ncmib sysctl call failed");
90          }
91          d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE);
92       }
93    }
94 }
95 
96 
ProcessList_new(UsersTable * usersTable,Hashtable * dynamicMeters,Hashtable * dynamicColumns,Hashtable * pidMatchList,uid_t userId)97 ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) {
98    const int fmib[] = { CTL_KERN, KERN_FSCALE };
99    size_t size;
100    char errbuf[_POSIX2_LINE_MAX];
101 
102    OpenBSDProcessList* opl = xCalloc(1, sizeof(OpenBSDProcessList));
103    ProcessList* pl = (ProcessList*) opl;
104    ProcessList_init(pl, Class(OpenBSDProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId);
105 
106    OpenBSDProcessList_updateCPUcount(pl);
107 
108    size = sizeof(fscale);
109    if (sysctl(fmib, 2, &fscale, &size, NULL, 0) < 0) {
110       CRT_fatalError("fscale sysctl call failed");
111    }
112 
113    if ((pageSize = sysconf(_SC_PAGESIZE)) == -1)
114       CRT_fatalError("pagesize sysconf call failed");
115    pageSizeKB = pageSize / ONE_K;
116 
117    opl->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
118    if (opl->kd == NULL) {
119       CRT_fatalError("kvm_openfiles() failed");
120    }
121 
122    opl->cpuSpeed = -1;
123 
124    return pl;
125 }
126 
ProcessList_delete(ProcessList * this)127 void ProcessList_delete(ProcessList* this) {
128    OpenBSDProcessList* opl = (OpenBSDProcessList*) this;
129 
130    if (opl->kd) {
131       kvm_close(opl->kd);
132    }
133 
134    free(opl->cpuData);
135 
136    ProcessList_done(this);
137    free(this);
138 }
139 
OpenBSDProcessList_scanMemoryInfo(ProcessList * pl)140 static void OpenBSDProcessList_scanMemoryInfo(ProcessList* pl) {
141    const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP };
142    struct uvmexp uvmexp;
143    size_t size_uvmexp = sizeof(uvmexp);
144 
145    if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {
146       CRT_fatalError("uvmexp sysctl call failed");
147    }
148 
149    pl->totalMem = uvmexp.npages * pageSizeKB;
150    pl->usedMem = (uvmexp.npages - uvmexp.free - uvmexp.paging) * pageSizeKB;
151 
152    // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9)
153    const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT };
154    struct bcachestats bcstats;
155    size_t size_bcstats = sizeof(bcstats);
156 
157    if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {
158       CRT_fatalError("cannot get vfs.bcachestat");
159    }
160 
161    pl->cachedMem = bcstats.numbufpages * pageSizeKB;
162 
163    /*
164     * Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>
165     * All rights reserved.
166     *
167     * Taken almost directly from OpenBSD's top(1)
168     *
169     * Originally released under a BSD-3 license
170     * Modified through htop developers applying GPL-2
171     */
172    int nswap = swapctl(SWAP_NSWAP, 0, 0);
173    if (nswap > 0) {
174       struct swapent swdev[nswap];
175       int rnswap = swapctl(SWAP_STATS, swdev, nswap);
176 
177       /* Total things up */
178       unsigned long long int total = 0, used = 0;
179       for (int i = 0; i < rnswap; i++) {
180          if (swdev[i].se_flags & SWF_ENABLE) {
181             used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));
182             total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));
183          }
184       }
185 
186       pl->totalSwap = total;
187       pl->usedSwap = used;
188    } else {
189       pl->totalSwap = pl->usedSwap = 0;
190    }
191 }
192 
OpenBSDProcessList_updateCwd(const struct kinfo_proc * kproc,Process * proc)193 static void OpenBSDProcessList_updateCwd(const struct kinfo_proc* kproc, Process* proc) {
194    const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid };
195    char buffer[2048];
196    size_t size = sizeof(buffer);
197    if (sysctl(mib, 3, buffer, &size, NULL, 0) != 0) {
198       free(proc->procCwd);
199       proc->procCwd = NULL;
200       return;
201    }
202 
203    /* Kernel threads return an empty buffer */
204    if (buffer[0] == '\0') {
205       free(proc->procCwd);
206       proc->procCwd = NULL;
207       return;
208    }
209 
210    free_and_xStrdup(&proc->procCwd, buffer);
211 }
212 
OpenBSDProcessList_updateProcessName(kvm_t * kd,const struct kinfo_proc * kproc,Process * proc)213 static void OpenBSDProcessList_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {
214    Process_updateComm(proc, kproc->p_comm);
215 
216    /*
217     * Like OpenBSD's top(1), we try to fall back to the command name
218     * (argv[0]) if we fail to construct the full command.
219     */
220    char** arg = kvm_getargv(kd, kproc, 500);
221    if (arg == NULL || *arg == NULL) {
222       Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
223       return;
224    }
225 
226    size_t len = 0;
227    for (int i = 0; arg[i] != NULL; i++) {
228       len += strlen(arg[i]) + 1;   /* room for arg and trailing space or NUL */
229    }
230 
231    /* don't use xMalloc here - we want to handle huge argv's gracefully */
232    char* s;
233    if ((s = malloc(len)) == NULL) {
234       Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));
235       return;
236    }
237 
238    *s = '\0';
239 
240    int start = 0;
241    int end = 0;
242    for (int i = 0; arg[i] != NULL; i++) {
243       size_t n = strlcat(s, arg[i], len);
244       if (i == 0) {
245          end = MINIMUM(n, len - 1);
246          /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */
247          for (int j = end; j > 0; j--) {
248             if (arg[0][j] == ' ' && arg[0][j - 1] != '\\') {
249                end = (arg[0][j - 1] == ':') ? (j - 1) : j;
250             }
251          }
252       }
253       /* the trailing space should get truncated anyway */
254       strlcat(s, " ", len);
255    }
256 
257    Process_updateCmdline(proc, s, start, end);
258 
259    free(s);
260 }
261 
262 /*
263  * Taken from OpenBSD's ps(1).
264  */
getpcpu(const struct kinfo_proc * kp)265 static double getpcpu(const struct kinfo_proc* kp) {
266    if (fscale == 0)
267       return 0.0;
268 
269    return 100.0 * (double)kp->p_pctcpu / fscale;
270 }
271 
OpenBSDProcessList_scanProcs(OpenBSDProcessList * this)272 static void OpenBSDProcessList_scanProcs(OpenBSDProcessList* this) {
273    const Settings* settings = this->super.settings;
274    const bool hideKernelThreads = settings->hideKernelThreads;
275    const bool hideUserlandThreads = settings->hideUserlandThreads;
276    int count = 0;
277 
278    const struct kinfo_proc* kprocs = kvm_getprocs(this->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count);
279 
280    for (int i = 0; i < count; i++) {
281       const struct kinfo_proc* kproc = &kprocs[i];
282 
283       /* Ignore main threads */
284       if (kproc->p_tid != -1) {
285          Process* containingProcess = ProcessList_findProcess(&this->super, kproc->p_pid);
286          if (containingProcess) {
287             if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr)
288                continue;
289 
290             containingProcess->nlwp++;
291          }
292       }
293 
294       bool preExisting = false;
295       Process* proc = ProcessList_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new);
296       OpenBSDProcess* fp = (OpenBSDProcess*) proc;
297 
298       if (!preExisting) {
299          proc->ppid = kproc->p_ppid;
300          proc->tpgid = kproc->p_tpgid;
301          proc->tgid = kproc->p_pid;
302          proc->session = kproc->p_sid;
303          proc->pgrp = kproc->p__pgid;
304          proc->isKernelThread = proc->pgrp == 0;
305          proc->isUserlandThread = kproc->p_tid != -1;
306          proc->starttime_ctime = kproc->p_ustart_sec;
307          Process_fillStarttimeBuffer(proc);
308          ProcessList_add(&this->super, proc);
309 
310          OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
311 
312          if (settings->flags & PROCESS_FLAG_CWD) {
313             OpenBSDProcessList_updateCwd(kproc, proc);
314          }
315 
316          proc->tty_nr = kproc->p_tdev;
317          const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;
318          if (!name || String_eq(name, "??")) {
319             free(proc->tty_name);
320             proc->tty_name = NULL;
321          } else {
322             free_and_xStrdup(&proc->tty_name, name);
323          }
324       } else {
325          if (settings->updateProcessNames) {
326             OpenBSDProcessList_updateProcessName(this->kd, kproc, proc);
327          }
328       }
329 
330       fp->addr = kproc->p_addr;
331       proc->m_virt = kproc->p_vm_dsize * pageSizeKB;
332       proc->m_resident = kproc->p_vm_rssize * pageSizeKB;
333       proc->percent_mem = proc->m_resident / (float)this->super.totalMem * 100.0F;
334       proc->percent_cpu = CLAMP(getpcpu(kproc), 0.0F, this->super.activeCPUs * 100.0F);
335       proc->nice = kproc->p_nice - 20;
336       proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));
337       proc->priority = kproc->p_priority - PZERO;
338       proc->processor = kproc->p_cpuid;
339       proc->minflt = kproc->p_uru_minflt;
340       proc->majflt = kproc->p_uru_majflt;
341       proc->nlwp = 1;
342 
343       if (proc->st_uid != kproc->p_uid) {
344          proc->st_uid = kproc->p_uid;
345          proc->user = UsersTable_getRef(this->super.usersTable, proc->st_uid);
346       }
347 
348       /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */
349       switch (kproc->p_stat) {
350          case SIDL:    proc->state = IDLE; break;
351          case SRUN:    proc->state = RUNNABLE; break;
352          case SSLEEP:  proc->state = SLEEPING; break;
353          case SSTOP:   proc->state = STOPPED; break;
354          case SZOMB:   proc->state = ZOMBIE; break;
355          case SDEAD:   proc->state = DEFUNCT; break;
356          case SONPROC: proc->state = RUNNING; break;
357          default:      proc->state = UNKNOWN;
358       }
359 
360       if (Process_isKernelThread(proc)) {
361          this->super.kernelThreads++;
362       } else if (Process_isUserlandThread(proc)) {
363          this->super.userlandThreads++;
364       }
365 
366       this->super.totalTasks++;
367       if (proc->state == RUNNING) {
368          this->super.runningTasks++;
369       }
370 
371       proc->show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));
372       proc->updated = true;
373    }
374 }
375 
getKernelCPUTimes(unsigned int cpuId,u_int64_t * times)376 static void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) {
377    const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId };
378    size_t length = sizeof(*times) * CPUSTATES;
379    if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {
380       CRT_fatalError("sysctl kern.cp_time2 failed");
381    }
382 }
383 
kernelCPUTimesToHtop(const u_int64_t * times,CPUData * cpu)384 static void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {
385    unsigned long long totalTime = 0;
386    for (int i = 0; i < CPUSTATES; i++) {
387       totalTime += times[i];
388    }
389 
390    unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];
391 
392    // XXX Not sure if CP_SPIN should be added to sysAllTime.
393    // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c
394    #ifdef CP_SPIN
395    sysAllTime += times[CP_SPIN];
396    #endif
397 
398    cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);
399    cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);
400    cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);
401    cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);
402    cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);
403    #ifdef CP_SPIN
404    cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime);
405    #endif
406    cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);
407    cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);
408 
409    cpu->totalTime = totalTime;
410    cpu->userTime = times[CP_USER];
411    cpu->niceTime = times[CP_NICE];
412    cpu->sysTime = times[CP_SYS];
413    cpu->sysAllTime = sysAllTime;
414    #ifdef CP_SPIN
415    cpu->spinTime = times[CP_SPIN];
416    #endif
417    cpu->intrTime = times[CP_INTR];
418    cpu->idleTime = times[CP_IDLE];
419 }
420 
OpenBSDProcessList_scanCPUTime(OpenBSDProcessList * this)421 static void OpenBSDProcessList_scanCPUTime(OpenBSDProcessList* this) {
422    u_int64_t kernelTimes[CPUSTATES] = {0};
423    u_int64_t avg[CPUSTATES] = {0};
424 
425    for (unsigned int i = 0; i < this->super.existingCPUs; i++) {
426       CPUData* cpu = &this->cpuData[i + 1];
427 
428       if (!cpu->online) {
429          continue;
430       }
431 
432       getKernelCPUTimes(i, kernelTimes);
433       kernelCPUTimesToHtop(kernelTimes, cpu);
434 
435       avg[CP_USER] += cpu->userTime;
436       avg[CP_NICE] += cpu->niceTime;
437       avg[CP_SYS] += cpu->sysTime;
438       #ifdef CP_SPIN
439       avg[CP_SPIN] += cpu->spinTime;
440       #endif
441       avg[CP_INTR] += cpu->intrTime;
442       avg[CP_IDLE] += cpu->idleTime;
443    }
444 
445    for (int i = 0; i < CPUSTATES; i++) {
446       avg[i] /= this->super.activeCPUs;
447    }
448 
449    kernelCPUTimesToHtop(avg, &this->cpuData[0]);
450 
451    {
452       const int mib[] = { CTL_HW, HW_CPUSPEED };
453       int cpuSpeed;
454       size_t size = sizeof(cpuSpeed);
455       if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) {
456          this->cpuSpeed = -1;
457       } else {
458          this->cpuSpeed = cpuSpeed;
459       }
460    }
461 }
462 
ProcessList_goThroughEntries(ProcessList * super,bool pauseProcessUpdate)463 void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) {
464    OpenBSDProcessList* opl = (OpenBSDProcessList*) super;
465 
466    OpenBSDProcessList_updateCPUcount(super);
467    OpenBSDProcessList_scanMemoryInfo(super);
468    OpenBSDProcessList_scanCPUTime(opl);
469 
470    // in pause mode only gather global data for meters (CPU/memory/...)
471    if (pauseProcessUpdate) {
472       return;
473    }
474 
475    OpenBSDProcessList_scanProcs(opl);
476 }
477 
ProcessList_isCPUonline(const ProcessList * super,unsigned int id)478 bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) {
479    assert(id < super->existingCPUs);
480 
481    const OpenBSDProcessList* opl = (const OpenBSDProcessList*) super;
482    return opl->cpuData[id + 1].online;
483 }
484