1 /*
2  * Copyright (c) 2009, Giampaolo Rodola', Landry Breuil.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  *
7  * Platform-specific module methods for NetBSD.
8  */
9 
10 #if defined(PSUTIL_NETBSD)
11     #define _KMEMUSER
12 #endif
13 
14 #include <Python.h>
15 #include <assert.h>
16 #include <err.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/param.h>
24 #include <sys/sysctl.h>
25 #include <sys/proc.h>
26 #include <sys/swap.h>  // for swap_mem
27 #include <signal.h>
28 #include <kvm.h>
29 // connection stuff
30 #include <netdb.h>  // for NI_MAXHOST
31 #include <sys/socket.h>
32 #include <sys/sched.h>  // for CPUSTATES & CP_*
33 #define _KERNEL  // for DTYPE_*
34     #include <sys/file.h>
35 #undef _KERNEL
36 #include <sys/disk.h>  // struct diskstats
37 #include <netinet/in.h>
38 #include <arpa/inet.h>
39 
40 #include "../../_psutil_common.h"
41 #include "../../_psutil_posix.h"
42 #include "specific.h"
43 
44 
45 #define PSUTIL_KPT2DOUBLE(t) (t ## _sec + t ## _usec / 1000000.0)
46 #define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0)
47 
48 
49 // ============================================================================
50 // Utility functions
51 // ============================================================================
52 
53 
54 int
psutil_kinfo_proc(pid_t pid,kinfo_proc * proc)55 psutil_kinfo_proc(pid_t pid, kinfo_proc *proc) {
56     // Fills a kinfo_proc struct based on process pid.
57     int ret;
58     int mib[6];
59     size_t size = sizeof(kinfo_proc);
60 
61     mib[0] = CTL_KERN;
62     mib[1] = KERN_PROC2;
63     mib[2] = KERN_PROC_PID;
64     mib[3] = pid;
65     mib[4] = size;
66     mib[5] = 1;
67 
68     ret = sysctl((int*)mib, 6, proc, &size, NULL, 0);
69     if (ret == -1) {
70         PyErr_SetFromErrno(PyExc_OSError);
71         return -1;
72     }
73     // sysctl stores 0 in the size if we can't find the process information.
74     if (size == 0) {
75         NoSuchProcess("sysctl (size = 0)");
76         return -1;
77     }
78     return 0;
79 }
80 
81 
82 struct kinfo_file *
kinfo_getfile(pid_t pid,int * cnt)83 kinfo_getfile(pid_t pid, int* cnt) {
84     // Mimic's FreeBSD kinfo_file call, taking a pid and a ptr to an
85     // int as arg and returns an array with cnt struct kinfo_file.
86     int mib[6];
87     size_t len;
88     struct kinfo_file* kf;
89     mib[0] = CTL_KERN;
90     mib[1] = KERN_FILE2;
91     mib[2] = KERN_FILE_BYPID;
92     mib[3] = (int) pid;
93     mib[4] = sizeof(struct kinfo_file);
94     mib[5] = 0;
95 
96     // get the size of what would be returned
97     if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
98         PyErr_SetFromErrno(PyExc_OSError);
99         return NULL;
100     }
101     if ((kf = malloc(len)) == NULL) {
102         PyErr_NoMemory();
103         return NULL;
104     }
105     mib[5] = (int)(len / sizeof(struct kinfo_file));
106     if (sysctl(mib, 6, kf, &len, NULL, 0) < 0) {
107         PyErr_SetFromErrno(PyExc_OSError);
108         return NULL;
109     }
110 
111     *cnt = (int)(len / sizeof(struct kinfo_file));
112     return kf;
113 }
114 
115 PyObject *
psutil_proc_cwd(PyObject * self,PyObject * args)116 psutil_proc_cwd(PyObject *self, PyObject *args) {
117     long pid;
118 
119     char path[MAXPATHLEN];
120     size_t pathlen = sizeof path;
121 
122     if (! PyArg_ParseTuple(args, "l", &pid))
123         return NULL;
124 
125 #ifdef KERN_PROC_CWD
126     int name[] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_CWD};
127     if (sysctl(name, 4, path, &pathlen, NULL, 0) != 0) {
128         if (errno == ENOENT)
129             NoSuchProcess("");
130         else
131             PyErr_SetFromErrno(PyExc_OSError);
132         return NULL;
133     }
134 #else
135     char *buf;
136     if (asprintf(&buf, "/proc/%d/cwd", (int)pid) < 0) {
137         PyErr_NoMemory();
138         return NULL;
139     }
140 
141     ssize_t len = readlink(buf, path, sizeof(path) - 1);
142     free(buf);
143     if (len == -1) {
144         if (errno == ENOENT)
145             NoSuchProcess("readlink (ENOENT)");
146         else
147             PyErr_SetFromErrno(PyExc_OSError);
148         return NULL;
149     }
150     path[len] = '\0';
151 #endif
152 
153     return PyUnicode_DecodeFSDefault(path);
154 }
155 
156 
157 // XXX: This is no longer used as per
158 // https://github.com/giampaolo/psutil/pull/557#issuecomment-171912820
159 // Current implementation uses /proc instead.
160 // Left here just in case.
161 /*
162 PyObject *
163 psutil_proc_exe(PyObject *self, PyObject *args) {
164 #if __NetBSD_Version__ >= 799000000
165     pid_t pid;
166     char pathname[MAXPATHLEN];
167     int error;
168     int mib[4];
169     int ret;
170     size_t size;
171 
172     if (! PyArg_ParseTuple(args, "l", &pid))
173         return NULL;
174     if (pid == 0) {
175         // else returns ENOENT
176         return Py_BuildValue("s", "");
177     }
178 
179     mib[0] = CTL_KERN;
180     mib[1] = KERN_PROC_ARGS;
181     mib[2] = pid;
182     mib[3] = KERN_PROC_PATHNAME;
183 
184     size = sizeof(pathname);
185     error = sysctl(mib, 4, NULL, &size, NULL, 0);
186     if (error == -1) {
187         PyErr_SetFromErrno(PyExc_OSError);
188         return NULL;
189     }
190 
191     error = sysctl(mib, 4, pathname, &size, NULL, 0);
192     if (error == -1) {
193         PyErr_SetFromErrno(PyExc_OSError);
194         return NULL;
195     }
196     if (size == 0 || strlen(pathname) == 0) {
197         ret = psutil_pid_exists(pid);
198         if (ret == -1)
199             return NULL;
200         else if (ret == 0)
201             return NoSuchProcess("psutil_pid_exists");
202         else
203             strcpy(pathname, "");
204     }
205 
206     return PyUnicode_DecodeFSDefault(pathname);
207 #else
208     return Py_BuildValue("s", "");
209 #endif
210 }
211 */
212 
213 PyObject *
psutil_proc_num_threads(PyObject * self,PyObject * args)214 psutil_proc_num_threads(PyObject *self, PyObject *args) {
215     // Return number of threads used by process as a Python integer.
216     long pid;
217     kinfo_proc kp;
218     if (! PyArg_ParseTuple(args, "l", &pid))
219         return NULL;
220     if (psutil_kinfo_proc(pid, &kp) == -1)
221         return NULL;
222     return Py_BuildValue("l", (long)kp.p_nlwps);
223 }
224 
225 PyObject *
psutil_proc_threads(PyObject * self,PyObject * args)226 psutil_proc_threads(PyObject *self, PyObject *args) {
227     pid_t pid;
228     int mib[5];
229     int i, nlwps;
230     ssize_t st;
231     size_t size;
232     struct kinfo_lwp *kl = NULL;
233     PyObject *py_retlist = PyList_New(0);
234     PyObject *py_tuple = NULL;
235 
236     if (py_retlist == NULL)
237         return NULL;
238     if (! PyArg_ParseTuple(args, "l", &pid))
239         goto error;
240 
241     mib[0] = CTL_KERN;
242     mib[1] = KERN_LWP;
243     mib[2] = pid;
244     mib[3] = sizeof(struct kinfo_lwp);
245     mib[4] = 0;
246 
247     st = sysctl(mib, 5, NULL, &size, NULL, 0);
248     if (st == -1) {
249         PyErr_SetFromErrno(PyExc_OSError);
250         goto error;
251     }
252     if (size == 0) {
253         NoSuchProcess("sysctl (size = 0)");
254         goto error;
255     }
256 
257     mib[4] = size / sizeof(size_t);
258     kl = malloc(size);
259     if (kl == NULL) {
260         PyErr_NoMemory();
261         goto error;
262     }
263 
264     st = sysctl(mib, 5, kl, &size, NULL, 0);
265     if (st == -1) {
266         PyErr_SetFromErrno(PyExc_OSError);
267         goto error;
268     }
269     if (size == 0) {
270         NoSuchProcess("sysctl (size = 0)");
271         goto error;
272     }
273 
274     nlwps = (int)(size / sizeof(struct kinfo_lwp));
275     for (i = 0; i < nlwps; i++) {
276         py_tuple = Py_BuildValue("idd",
277                                  (&kl[i])->l_lid,
278                                  PSUTIL_KPT2DOUBLE((&kl[i])->l_rtime),
279                                  PSUTIL_KPT2DOUBLE((&kl[i])->l_rtime));
280         if (py_tuple == NULL)
281             goto error;
282         if (PyList_Append(py_retlist, py_tuple))
283             goto error;
284         Py_DECREF(py_tuple);
285     }
286     free(kl);
287     return py_retlist;
288 
289 error:
290     Py_XDECREF(py_tuple);
291     Py_DECREF(py_retlist);
292     if (kl != NULL)
293         free(kl);
294     return NULL;
295 }
296 
297 
298 // ============================================================================
299 // APIS
300 // ============================================================================
301 
302 int
psutil_get_proc_list(kinfo_proc ** procList,size_t * procCount)303 psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
304     // Returns a list of all BSD processes on the system.  This routine
305     // allocates the list and puts it in *procList and a count of the
306     // number of entries in *procCount.  You are responsible for freeing
307     // this list (use "free" from System framework).
308     // On success, the function returns 0.
309     // On error, the function returns a BSD errno value.
310     kinfo_proc *result;
311     // Declaring name as const requires us to cast it when passing it to
312     // sysctl because the prototype doesn't include the const modifier.
313     char errbuf[_POSIX2_LINE_MAX];
314     int cnt;
315     kvm_t *kd;
316 
317     assert( procList != NULL);
318     assert(*procList == NULL);
319     assert(procCount != NULL);
320 
321     kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
322 
323     if (kd == NULL) {
324         PyErr_Format(
325             PyExc_RuntimeError, "kvm_openfiles() syscall failed: %s", errbuf);
326         return 1;
327     }
328 
329     result = kvm_getproc2(kd, KERN_PROC_ALL, 0, sizeof(kinfo_proc), &cnt);
330     if (result == NULL) {
331         PyErr_Format(PyExc_RuntimeError, "kvm_getproc2() syscall failed");
332         kvm_close(kd);
333         return 1;
334     }
335 
336     *procCount = (size_t)cnt;
337 
338     size_t mlen = cnt * sizeof(kinfo_proc);
339 
340     if ((*procList = malloc(mlen)) == NULL) {
341         PyErr_NoMemory();
342         kvm_close(kd);
343         return 1;
344     }
345 
346     memcpy(*procList, result, mlen);
347     assert(*procList != NULL);
348     kvm_close(kd);
349 
350     return 0;
351 }
352 
353 
354 char *
psutil_get_cmd_args(pid_t pid,size_t * argsize)355 psutil_get_cmd_args(pid_t pid, size_t *argsize) {
356     int mib[4];
357     int st;
358     size_t len;
359     char *procargs;
360 
361     mib[0] = CTL_KERN;
362     mib[1] = KERN_PROC_ARGS;
363     mib[2] = pid;
364     mib[3] = KERN_PROC_ARGV;
365     len = 0;
366 
367     st = sysctl(mib, __arraycount(mib), NULL, &len, NULL, 0);
368     if (st == -1) {
369         PyErr_SetFromErrno(PyExc_OSError);
370         return NULL;
371     }
372 
373     procargs = (char *)malloc(len);
374     if (procargs == NULL) {
375         PyErr_NoMemory();
376         return NULL;
377     }
378     st = sysctl(mib, __arraycount(mib), procargs, &len, NULL, 0);
379     if (st == -1) {
380         free(procargs);
381         PyErr_SetFromErrno(PyExc_OSError);
382         return NULL;
383     }
384 
385     *argsize = len;
386     return procargs;
387 }
388 
389 
390 // Return the command line as a python list object.
391 // XXX - most of the times sysctl() returns a truncated string.
392 // Also /proc/pid/cmdline behaves the same so it looks like this
393 // is a kernel bug.
394 PyObject *
psutil_get_cmdline(pid_t pid)395 psutil_get_cmdline(pid_t pid) {
396     char *argstr = NULL;
397     size_t pos = 0;
398     size_t argsize = 0;
399     PyObject *py_arg = NULL;
400     PyObject *py_retlist = PyList_New(0);
401 
402     if (py_retlist == NULL)
403         return NULL;
404     if (pid == 0)
405         return py_retlist;
406 
407     argstr = psutil_get_cmd_args(pid, &argsize);
408     if (argstr == NULL)
409         goto error;
410 
411     // args are returned as a flattened string with \0 separators between
412     // arguments add each string to the list then step forward to the next
413     // separator
414     if (argsize > 0) {
415         while (pos < argsize) {
416             py_arg = PyUnicode_DecodeFSDefault(&argstr[pos]);
417             if (!py_arg)
418                 goto error;
419             if (PyList_Append(py_retlist, py_arg))
420                 goto error;
421             Py_DECREF(py_arg);
422             pos = pos + strlen(&argstr[pos]) + 1;
423         }
424     }
425 
426     free(argstr);
427     return py_retlist;
428 
429 error:
430     Py_XDECREF(py_arg);
431     Py_DECREF(py_retlist);
432     if (argstr != NULL)
433         free(argstr);
434     return NULL;
435 }
436 
437 
438 /*
439  * Virtual memory stats, taken from:
440  * https://github.com/satterly/zabbix-stats/blob/master/src/libs/zbxsysinfo/
441  *     netbsd/memory.c
442  */
443 PyObject *
psutil_virtual_mem(PyObject * self,PyObject * args)444 psutil_virtual_mem(PyObject *self, PyObject *args) {
445     size_t size;
446     struct uvmexp_sysctl uv;
447     int mib[] = {CTL_VM, VM_UVMEXP2};
448     long pagesize = getpagesize();
449 
450     size = sizeof(uv);
451     if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) {
452         PyErr_SetFromErrno(PyExc_OSError);
453         return NULL;
454     }
455 
456     return Py_BuildValue("KKKKKKKK",
457         (unsigned long long) uv.npages << uv.pageshift,  // total
458         (unsigned long long) uv.free << uv.pageshift,  // free
459         (unsigned long long) uv.active << uv.pageshift,  // active
460         (unsigned long long) uv.inactive << uv.pageshift,  // inactive
461         (unsigned long long) uv.wired << uv.pageshift,  // wired
462         (unsigned long long) uv.filepages + uv.execpages * pagesize,  // cached
463         // These are determined from /proc/meminfo in Python.
464         (unsigned long long) 0,  // buffers
465         (unsigned long long) 0  // shared
466     );
467 }
468 
469 
470 PyObject *
psutil_swap_mem(PyObject * self,PyObject * args)471 psutil_swap_mem(PyObject *self, PyObject *args) {
472     uint64_t swap_total, swap_free;
473     struct swapent *swdev;
474     int nswap, i;
475 
476     nswap = swapctl(SWAP_NSWAP, 0, 0);
477     if (nswap == 0) {
478         // This means there's no swap partition.
479         return Py_BuildValue("(iiiii)", 0, 0, 0, 0, 0);
480     }
481 
482     swdev = calloc(nswap, sizeof(*swdev));
483     if (swdev == NULL) {
484         PyErr_SetFromErrno(PyExc_OSError);
485         return NULL;
486     }
487 
488     if (swapctl(SWAP_STATS, swdev, nswap) == -1) {
489         PyErr_SetFromErrno(PyExc_OSError);
490         goto error;
491     }
492 
493     // Total things up.
494     swap_total = swap_free = 0;
495     for (i = 0; i < nswap; i++) {
496         if (swdev[i].se_flags & SWF_ENABLE) {
497             swap_total += swdev[i].se_nblks * DEV_BSIZE;
498             swap_free += (swdev[i].se_nblks - swdev[i].se_inuse) * DEV_BSIZE;
499         }
500     }
501     free(swdev);
502 
503     // Get swap in/out
504     unsigned int total;
505     size_t size = sizeof(total);
506     struct uvmexp_sysctl uv;
507     int mib[] = {CTL_VM, VM_UVMEXP2};
508     long pagesize = getpagesize();
509     size = sizeof(uv);
510     if (sysctl(mib, 2, &uv, &size, NULL, 0) < 0) {
511         PyErr_SetFromErrno(PyExc_OSError);
512         goto error;
513     }
514 
515     return Py_BuildValue("(LLLll)",
516                          swap_total,
517                          (swap_total - swap_free),
518                          swap_free,
519                          (long) uv.pgswapin * pagesize,  // swap in
520                          (long) uv.pgswapout * pagesize);  // swap out
521 
522 error:
523     free(swdev);
524     return NULL;
525 }
526 
527 
528 PyObject *
psutil_proc_num_fds(PyObject * self,PyObject * args)529 psutil_proc_num_fds(PyObject *self, PyObject *args) {
530     long pid;
531     int cnt;
532 
533     struct kinfo_file *freep;
534 
535     if (! PyArg_ParseTuple(args, "l", &pid))
536         return NULL;
537 
538     errno = 0;
539     freep = kinfo_getfile(pid, &cnt);
540     if (freep == NULL) {
541         psutil_raise_for_pid(pid, "kinfo_getfile()");
542         return NULL;
543     }
544     free(freep);
545 
546     return Py_BuildValue("i", cnt);
547 }
548 
549 
550 PyObject *
psutil_per_cpu_times(PyObject * self,PyObject * args)551 psutil_per_cpu_times(PyObject *self, PyObject *args) {
552     // XXX: why static?
553     int mib[3];
554     int ncpu;
555     size_t len;
556     size_t size;
557     int i;
558     PyObject *py_cputime = NULL;
559     PyObject *py_retlist = PyList_New(0);
560 
561     if (py_retlist == NULL)
562         return NULL;
563     // retrieve the number of cpus
564     mib[0] = CTL_HW;
565     mib[1] = HW_NCPU;
566     len = sizeof(ncpu);
567     if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) {
568         PyErr_SetFromErrno(PyExc_OSError);
569         goto error;
570     }
571     uint64_t cpu_time[CPUSTATES];
572 
573     for (i = 0; i < ncpu; i++) {
574         // per-cpu info
575         mib[0] = CTL_KERN;
576         mib[1] = KERN_CP_TIME;
577         mib[2] = i;
578         size = sizeof(cpu_time);
579         if (sysctl(mib, 3, &cpu_time, &size, NULL, 0) == -1) {
580             warn("failed to get kern.cptime2");
581             PyErr_SetFromErrno(PyExc_OSError);
582             return NULL;
583         }
584 
585         py_cputime = Py_BuildValue(
586             "(ddddd)",
587             (double)cpu_time[CP_USER] / CLOCKS_PER_SEC,
588             (double)cpu_time[CP_NICE] / CLOCKS_PER_SEC,
589             (double)cpu_time[CP_SYS] / CLOCKS_PER_SEC,
590             (double)cpu_time[CP_IDLE] / CLOCKS_PER_SEC,
591             (double)cpu_time[CP_INTR] / CLOCKS_PER_SEC);
592         if (!py_cputime)
593             goto error;
594         if (PyList_Append(py_retlist, py_cputime))
595             goto error;
596         Py_DECREF(py_cputime);
597     }
598 
599     return py_retlist;
600 
601 error:
602     Py_XDECREF(py_cputime);
603     Py_DECREF(py_retlist);
604     return NULL;
605 }
606 
607 
608 PyObject *
psutil_disk_io_counters(PyObject * self,PyObject * args)609 psutil_disk_io_counters(PyObject *self, PyObject *args) {
610     int i, dk_ndrive, mib[3];
611     size_t len;
612     struct io_sysctl *stats = NULL;
613     PyObject *py_disk_info = NULL;
614     PyObject *py_retdict = PyDict_New();
615 
616     if (py_retdict == NULL)
617         return NULL;
618     mib[0] = CTL_HW;
619     mib[1] = HW_IOSTATS;
620     mib[2] = sizeof(struct io_sysctl);
621     len = 0;
622     if (sysctl(mib, 3, NULL, &len, NULL, 0) < 0) {
623         warn("can't get HW_IOSTATS");
624         PyErr_SetFromErrno(PyExc_OSError);
625         goto error;
626     }
627     dk_ndrive = (int)(len / sizeof(struct io_sysctl));
628 
629     stats = malloc(len);
630     if (stats == NULL) {
631         PyErr_NoMemory();
632         goto error;
633     }
634     if (sysctl(mib, 3, stats, &len, NULL, 0) < 0 ) {
635         PyErr_SetFromErrno(PyExc_OSError);
636         goto error;
637     }
638 
639     for (i = 0; i < dk_ndrive; i++) {
640         py_disk_info = Py_BuildValue(
641             "(KKKK)",
642             stats[i].rxfer,
643             stats[i].wxfer,
644             stats[i].rbytes,
645             stats[i].wbytes
646         );
647         if (!py_disk_info)
648             goto error;
649         if (PyDict_SetItemString(py_retdict, stats[i].name, py_disk_info))
650             goto error;
651         Py_DECREF(py_disk_info);
652     }
653 
654     free(stats);
655     return py_retdict;
656 
657 error:
658     Py_XDECREF(py_disk_info);
659     Py_DECREF(py_retdict);
660     if (stats != NULL)
661         free(stats);
662     return NULL;
663 }
664 
665 
666 PyObject *
psutil_cpu_stats(PyObject * self,PyObject * args)667 psutil_cpu_stats(PyObject *self, PyObject *args) {
668     size_t size;
669     struct uvmexp_sysctl uv;
670     int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2};
671 
672     size = sizeof(uv);
673     if (sysctl(uvmexp_mib, 2, &uv, &size, NULL, 0) < 0) {
674         PyErr_SetFromErrno(PyExc_OSError);
675         return NULL;
676     }
677 
678     return Py_BuildValue(
679         "IIIIIII",
680         uv.swtch,  // ctx switches
681         uv.intrs,  // interrupts - XXX always 0, will be determined via /proc
682         uv.softs,  // soft interrupts
683         uv.syscalls,  // syscalls - XXX always 0
684         uv.traps,  // traps
685         uv.faults,  // faults
686         uv.forks  // forks
687     );
688 }
689