1 /*
2  * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
3  * Use of this source code is governed by a BSD-style license that can be
4  * found in the LICENSE file.
5  *
6  * Linux-specific functions.
7  */
8 
9 #ifndef _GNU_SOURCE
10     #define _GNU_SOURCE 1
11 #endif
12 #include <Python.h>
13 #include <errno.h>
14 #include <stdlib.h>
15 #include <mntent.h>
16 #include <features.h>
17 #include <utmp.h>
18 #include <sched.h>
19 #include <linux/version.h>
20 #include <sys/syscall.h>
21 #include <sys/sysinfo.h>
22 #include <sys/ioctl.h>
23 #include <sys/socket.h>
24 #include <linux/sockios.h>
25 #include <linux/if.h>
26 #include <sys/resource.h>
27 
28 // see: https://github.com/giampaolo/psutil/issues/659
29 #ifdef PSUTIL_ETHTOOL_MISSING_TYPES
30     #include <linux/types.h>
31     typedef __u64 u64;
32     typedef __u32 u32;
33     typedef __u16 u16;
34     typedef __u8 u8;
35 #endif
36 /* Avoid redefinition of struct sysinfo with musl libc */
37 #define _LINUX_SYSINFO_H
38 #include <linux/ethtool.h>
39 
40 /* The minimum number of CPUs allocated in a cpu_set_t */
41 static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT;
42 
43 // Linux >= 2.6.13
44 #define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set)
45 
46 // Should exist starting from CentOS 6 (year 2011).
47 #ifdef CPU_ALLOC
48     #define PSUTIL_HAVE_CPU_AFFINITY
49 #endif
50 
51 #include "_psutil_common.h"
52 #include "_psutil_posix.h"
53 
54 // May happen on old RedHat versions, see:
55 // https://github.com/giampaolo/psutil/issues/607
56 #ifndef DUPLEX_UNKNOWN
57     #define DUPLEX_UNKNOWN 0xff
58 #endif
59 
60 
61 #if PSUTIL_HAVE_IOPRIO
62 enum {
63     IOPRIO_WHO_PROCESS = 1,
64 };
65 
66 static inline int
67 ioprio_get(int which, int who) {
68     return syscall(__NR_ioprio_get, which, who);
69 }
70 
71 static inline int
72 ioprio_set(int which, int who, int ioprio) {
73     return syscall(__NR_ioprio_set, which, who, ioprio);
74 }
75 
76 #define IOPRIO_CLASS_SHIFT 13
77 #define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
78 
79 #define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
80 #define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
81 #define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
82 
83 
84 /*
85  * Return a (ioclass, iodata) Python tuple representing process I/O priority.
86  */
87 static PyObject *
88 psutil_proc_ioprio_get(PyObject *self, PyObject *args) {
89     pid_t pid;
90     int ioprio, ioclass, iodata;
91     if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
92         return NULL;
93     ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid);
94     if (ioprio == -1)
95         return PyErr_SetFromErrno(PyExc_OSError);
96     ioclass = IOPRIO_PRIO_CLASS(ioprio);
97     iodata = IOPRIO_PRIO_DATA(ioprio);
98     return Py_BuildValue("ii", ioclass, iodata);
99 }
100 
101 
102 /*
103  * A wrapper around ioprio_set(); sets process I/O priority.
104  * ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE
105  * or 0. iodata goes from 0 to 7 depending on ioclass specified.
106  */
107 static PyObject *
108 psutil_proc_ioprio_set(PyObject *self, PyObject *args) {
109     pid_t pid;
110     int ioprio, ioclass, iodata;
111     int retval;
112 
113     if (! PyArg_ParseTuple(
114             args, _Py_PARSE_PID "ii", &pid, &ioclass, &iodata)) {
115         return NULL;
116     }
117     ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata);
118     retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio);
119     if (retval == -1)
120         return PyErr_SetFromErrno(PyExc_OSError);
121     Py_RETURN_NONE;
122 }
123 #endif
124 
125 
126 /*
127  * Return disk mounted partitions as a list of tuples including device,
128  * mount point and filesystem type
129  */
130 static PyObject *
131 psutil_disk_partitions(PyObject *self, PyObject *args) {
132     FILE *file = NULL;
133     struct mntent *entry;
134     char *mtab_path;
135     PyObject *py_dev = NULL;
136     PyObject *py_mountp = NULL;
137     PyObject *py_tuple = NULL;
138     PyObject *py_retlist = PyList_New(0);
139 
140     if (py_retlist == NULL)
141         return NULL;
142 
143     if (!PyArg_ParseTuple(args, "s", &mtab_path))
144         return NULL;
145 
146     Py_BEGIN_ALLOW_THREADS
147     file = setmntent(mtab_path, "r");
148     Py_END_ALLOW_THREADS
149     if ((file == 0) || (file == NULL)) {
150         psutil_debug("setmntent() failed");
151         PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path);
152         goto error;
153     }
154 
155     while ((entry = getmntent(file))) {
156         if (entry == NULL) {
157             PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed");
158             goto error;
159         }
160         py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname);
161         if (! py_dev)
162             goto error;
163         py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir);
164         if (! py_mountp)
165             goto error;
166         py_tuple = Py_BuildValue("(OOss)",
167                                  py_dev,             // device
168                                  py_mountp,          // mount point
169                                  entry->mnt_type,    // fs type
170                                  entry->mnt_opts);   // options
171         if (! py_tuple)
172             goto error;
173         if (PyList_Append(py_retlist, py_tuple))
174             goto error;
175         Py_CLEAR(py_dev);
176         Py_CLEAR(py_mountp);
177         Py_CLEAR(py_tuple);
178     }
179     endmntent(file);
180     return py_retlist;
181 
182 error:
183     if (file != NULL)
184         endmntent(file);
185     Py_XDECREF(py_dev);
186     Py_XDECREF(py_mountp);
187     Py_XDECREF(py_tuple);
188     Py_DECREF(py_retlist);
189     return NULL;
190 }
191 
192 
193 /*
194  * A wrapper around sysinfo(), return system memory usage statistics.
195  */
196 static PyObject *
197 psutil_linux_sysinfo(PyObject *self, PyObject *args) {
198     struct sysinfo info;
199 
200     if (sysinfo(&info) != 0)
201         return PyErr_SetFromErrno(PyExc_OSError);
202     // note: boot time might also be determined from here
203     return Py_BuildValue(
204         "(kkkkkkI)",
205         info.totalram,  // total
206         info.freeram,  // free
207         info.bufferram, // buffer
208         info.sharedram, // shared
209         info.totalswap, // swap tot
210         info.freeswap,  // swap free
211         info.mem_unit  // multiplier
212     );
213 }
214 
215 
216 /*
217  * Return process CPU affinity as a Python list
218  */
219 #ifdef PSUTIL_HAVE_CPU_AFFINITY
220 
221 static PyObject *
222 psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) {
223     int cpu, ncpus, count, cpucount_s;
224     pid_t pid;
225     size_t setsize;
226     cpu_set_t *mask = NULL;
227     PyObject *py_list = NULL;
228 
229     if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid))
230         return NULL;
231     ncpus = NCPUS_START;
232     while (1) {
233         setsize = CPU_ALLOC_SIZE(ncpus);
234         mask = CPU_ALLOC(ncpus);
235         if (mask == NULL) {
236             psutil_debug("CPU_ALLOC() failed");
237             return PyErr_NoMemory();
238         }
239         if (sched_getaffinity(pid, setsize, mask) == 0)
240             break;
241         CPU_FREE(mask);
242         if (errno != EINVAL)
243             return PyErr_SetFromErrno(PyExc_OSError);
244         if (ncpus > INT_MAX / 2) {
245             PyErr_SetString(PyExc_OverflowError, "could not allocate "
246                             "a large enough CPU set");
247             return NULL;
248         }
249         ncpus = ncpus * 2;
250     }
251 
252     py_list = PyList_New(0);
253     if (py_list == NULL)
254         goto error;
255 
256     cpucount_s = CPU_COUNT_S(setsize, mask);
257     for (cpu = 0, count = cpucount_s; count; cpu++) {
258         if (CPU_ISSET_S(cpu, setsize, mask)) {
259 #if PY_MAJOR_VERSION >= 3
260             PyObject *cpu_num = PyLong_FromLong(cpu);
261 #else
262             PyObject *cpu_num = PyInt_FromLong(cpu);
263 #endif
264             if (cpu_num == NULL)
265                 goto error;
266             if (PyList_Append(py_list, cpu_num)) {
267                 Py_DECREF(cpu_num);
268                 goto error;
269             }
270             Py_DECREF(cpu_num);
271             --count;
272         }
273     }
274     CPU_FREE(mask);
275     return py_list;
276 
277 error:
278     if (mask)
279         CPU_FREE(mask);
280     Py_XDECREF(py_list);
281     return NULL;
282 }
283 
284 
285 /*
286  * Set process CPU affinity; expects a bitmask
287  */
288 static PyObject *
289 psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) {
290     cpu_set_t cpu_set;
291     size_t len;
292     pid_t pid;
293     int i, seq_len;
294     PyObject *py_cpu_set;
295     PyObject *py_cpu_seq = NULL;
296 
297     if (!PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &py_cpu_set))
298         return NULL;
299 
300     if (!PySequence_Check(py_cpu_set)) {
301         PyErr_Format(PyExc_TypeError, "sequence argument expected, got %s",
302                      Py_TYPE(py_cpu_set)->tp_name);
303         goto error;
304     }
305 
306     py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer");
307     if (!py_cpu_seq)
308         goto error;
309     seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq);
310     CPU_ZERO(&cpu_set);
311     for (i = 0; i < seq_len; i++) {
312         PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i);
313 #if PY_MAJOR_VERSION >= 3
314         long value = PyLong_AsLong(item);
315 #else
316         long value = PyInt_AsLong(item);
317 #endif
318         if ((value == -1) || PyErr_Occurred()) {
319             if (!PyErr_Occurred())
320                 PyErr_SetString(PyExc_ValueError, "invalid CPU value");
321             goto error;
322         }
323         CPU_SET(value, &cpu_set);
324     }
325 
326     len = sizeof(cpu_set);
327     if (sched_setaffinity(pid, len, &cpu_set)) {
328         PyErr_SetFromErrno(PyExc_OSError);
329         goto error;
330     }
331 
332     Py_DECREF(py_cpu_seq);
333     Py_RETURN_NONE;
334 
335 error:
336     if (py_cpu_seq != NULL)
337         Py_DECREF(py_cpu_seq);
338     return NULL;
339 }
340 #endif  /* PSUTIL_HAVE_CPU_AFFINITY */
341 
342 
343 /*
344  * Return currently connected users as a list of tuples.
345  */
346 static PyObject *
347 psutil_users(PyObject *self, PyObject *args) {
348     struct utmp *ut;
349     PyObject *py_retlist = PyList_New(0);
350     PyObject *py_tuple = NULL;
351     PyObject *py_username = NULL;
352     PyObject *py_tty = NULL;
353     PyObject *py_hostname = NULL;
354     PyObject *py_user_proc = NULL;
355 
356     if (py_retlist == NULL)
357         return NULL;
358     setutent();
359     while (NULL != (ut = getutent())) {
360         py_tuple = NULL;
361         py_user_proc = NULL;
362         if (ut->ut_type == USER_PROCESS)
363             py_user_proc = Py_True;
364         else
365             py_user_proc = Py_False;
366         py_username = PyUnicode_DecodeFSDefault(ut->ut_user);
367         if (! py_username)
368             goto error;
369         py_tty = PyUnicode_DecodeFSDefault(ut->ut_line);
370         if (! py_tty)
371             goto error;
372         py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host);
373         if (! py_hostname)
374             goto error;
375 
376         py_tuple = Py_BuildValue(
377             "OOOfO" _Py_PARSE_PID,
378             py_username,              // username
379             py_tty,                   // tty
380             py_hostname,              // hostname
381             (float)ut->ut_tv.tv_sec,  // tstamp
382             py_user_proc,             // (bool) user process
383             ut->ut_pid                // process id
384         );
385         if (! py_tuple)
386             goto error;
387         if (PyList_Append(py_retlist, py_tuple))
388             goto error;
389         Py_CLEAR(py_username);
390         Py_CLEAR(py_tty);
391         Py_CLEAR(py_hostname);
392         Py_CLEAR(py_tuple);
393     }
394     endutent();
395     return py_retlist;
396 
397 error:
398     Py_XDECREF(py_username);
399     Py_XDECREF(py_tty);
400     Py_XDECREF(py_hostname);
401     Py_XDECREF(py_tuple);
402     Py_DECREF(py_retlist);
403     endutent();
404     return NULL;
405 }
406 
407 
408 /*
409  * Return stats about a particular network
410  * interface.  References:
411  * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py
412  * http://www.i-scream.org/libstatgrab/
413  */
414 static PyObject*
415 psutil_net_if_duplex_speed(PyObject* self, PyObject* args) {
416     char *nic_name;
417     int sock = 0;
418     int ret;
419     int duplex;
420     int speed;
421     struct ifreq ifr;
422     struct ethtool_cmd ethcmd;
423     PyObject *py_retlist = NULL;
424 
425     if (! PyArg_ParseTuple(args, "s", &nic_name))
426         return NULL;
427 
428     sock = socket(AF_INET, SOCK_DGRAM, 0);
429     if (sock == -1)
430         return PyErr_SetFromOSErrnoWithSyscall("socket()");
431     PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
432 
433     // duplex and speed
434     memset(&ethcmd, 0, sizeof ethcmd);
435     ethcmd.cmd = ETHTOOL_GSET;
436     ifr.ifr_data = (void *)&ethcmd;
437     ret = ioctl(sock, SIOCETHTOOL, &ifr);
438 
439     if (ret != -1) {
440         duplex = ethcmd.duplex;
441         speed = ethcmd.speed;
442     }
443     else {
444         if ((errno == EOPNOTSUPP) || (errno == EINVAL)) {
445             // EOPNOTSUPP may occur in case of wi-fi cards.
446             // For EINVAL see:
447             // https://github.com/giampaolo/psutil/issues/797
448             //     #issuecomment-202999532
449             duplex = DUPLEX_UNKNOWN;
450             speed = 0;
451         }
452         else {
453             PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)");
454             goto error;
455         }
456     }
457 
458     py_retlist = Py_BuildValue("[ii]", duplex, speed);
459     if (!py_retlist)
460         goto error;
461     close(sock);
462     return py_retlist;
463 
464 error:
465     if (sock != -1)
466         close(sock);
467     return NULL;
468 }
469 
470 
471 /*
472  * Module init.
473  */
474 
475 static PyMethodDef mod_methods[] = {
476     // --- per-process functions
477 
478 #if PSUTIL_HAVE_IOPRIO
479     {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS,
480      "Get process I/O priority"},
481     {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS,
482      "Set process I/O priority"},
483 #endif
484 #ifdef PSUTIL_HAVE_CPU_AFFINITY
485     {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS,
486      "Return process CPU affinity as a Python long (the bitmask)."},
487     {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS,
488      "Set process CPU affinity; expects a bitmask."},
489 #endif
490 
491     // --- system related functions
492 
493     {"disk_partitions", psutil_disk_partitions, METH_VARARGS,
494      "Return disk mounted partitions as a list of tuples including "
495      "device, mount point and filesystem type"},
496     {"users", psutil_users, METH_VARARGS,
497      "Return currently connected users as a list of tuples"},
498     {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS,
499      "Return duplex and speed info about a NIC"},
500 
501     // --- linux specific
502 
503     {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS,
504      "A wrapper around sysinfo(), return system memory usage statistics"},
505     // --- others
506     {"set_testing", psutil_set_testing, METH_NOARGS,
507      "Set psutil in testing mode"},
508 
509     {NULL, NULL, 0, NULL}
510 };
511 
512 
513 #if PY_MAJOR_VERSION >= 3
514     #define INITERR return NULL
515 
516     static struct PyModuleDef moduledef = {
517         PyModuleDef_HEAD_INIT,
518         "_psutil_linux",
519         NULL,
520         -1,
521         mod_methods,
522         NULL,
523         NULL,
524         NULL,
525         NULL
526     };
527 
528     PyObject *PyInit__psutil_linux(void)
529 #else  /* PY_MAJOR_VERSION */
530     #define INITERR return
531 
532     void init_psutil_linux(void)
533 #endif  /* PY_MAJOR_VERSION */
534 {
535 #if PY_MAJOR_VERSION >= 3
536     PyObject *mod = PyModule_Create(&moduledef);
537 #else
538     PyObject *mod = Py_InitModule("_psutil_linux", mod_methods);
539 #endif
540     if (mod == NULL)
541         INITERR;
542 
543     if (PyModule_AddIntConstant(mod, "version", PSUTIL_VERSION)) INITERR;
544     if (PyModule_AddIntConstant(mod, "DUPLEX_HALF", DUPLEX_HALF)) INITERR;
545     if (PyModule_AddIntConstant(mod, "DUPLEX_FULL", DUPLEX_FULL)) INITERR;
546     if (PyModule_AddIntConstant(mod, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN)) INITERR;
547 
548     psutil_setup();
549 
550     if (mod == NULL)
551         INITERR;
552 #if PY_MAJOR_VERSION >= 3
553     return mod;
554 #endif
555 }
556