1 /*
2  * Copyright (c) 2009, Jay Loden, 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  * Helper functions related to fetching process information.
7  * Used by _psutil_osx module methods.
8  */
9 
10 
11 #include <Python.h>
12 #include <assert.h>
13 #include <errno.h>
14 #include <limits.h>  // for INT_MAX
15 #include <stdbool.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <signal.h>
19 #include <sys/sysctl.h>
20 #include <libproc.h>
21 
22 #include "process_info.h"
23 #include "../../_psutil_common.h"
24 #include "../../_psutil_posix.h"
25 
26 /*
27  * Returns a list of all BSD processes on the system.  This routine
28  * allocates the list and puts it in *procList and a count of the
29  * number of entries in *procCount.  You are responsible for freeing
30  * this list (use "free" from System framework).
31  * On success, the function returns 0.
32  * On error, the function returns a BSD errno value.
33  */
34 int
psutil_get_proc_list(kinfo_proc ** procList,size_t * procCount)35 psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) {
36     int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
37     size_t size, size2;
38     void *ptr;
39     int err;
40     int lim = 8;  // some limit
41 
42     assert( procList != NULL);
43     assert(*procList == NULL);
44     assert(procCount != NULL);
45 
46     *procCount = 0;
47 
48     /*
49      * We start by calling sysctl with ptr == NULL and size == 0.
50      * That will succeed, and set size to the appropriate length.
51      * We then allocate a buffer of at least that size and call
52      * sysctl with that buffer.  If that succeeds, we're done.
53      * If that call fails with ENOMEM, we throw the buffer away
54      * and try again.
55      * Note that the loop calls sysctl with NULL again.  This is
56      * is necessary because the ENOMEM failure case sets size to
57      * the amount of data returned, not the amount of data that
58      * could have been returned.
59      */
60     while (lim-- > 0) {
61         size = 0;
62         if (sysctl((int *)mib3, 3, NULL, &size, NULL, 0) == -1) {
63             PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)");
64             return 1;
65         }
66         size2 = size + (size >> 3);  // add some
67         if (size2 > size) {
68             ptr = malloc(size2);
69             if (ptr == NULL)
70                 ptr = malloc(size);
71             else
72                 size = size2;
73         }
74         else {
75             ptr = malloc(size);
76         }
77         if (ptr == NULL) {
78             PyErr_NoMemory();
79             return 1;
80         }
81 
82         if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) {
83             err = errno;
84             free(ptr);
85             if (err != ENOMEM) {
86                 PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)");
87                 return 1;
88             }
89         }
90         else {
91             *procList = (kinfo_proc *)ptr;
92             *procCount = size / sizeof(kinfo_proc);
93             if (procCount <= 0) {
94                 PyErr_Format(PyExc_RuntimeError, "no PIDs found");
95                 return 1;
96             }
97             return 0;  // success
98         }
99     }
100 
101     PyErr_Format(PyExc_RuntimeError, "couldn't collect PIDs list");
102     return 1;
103 }
104 
105 
106 // Read the maximum argument size for processes
107 int
psutil_get_argmax()108 psutil_get_argmax() {
109     int argmax;
110     int mib[] = { CTL_KERN, KERN_ARGMAX };
111     size_t size = sizeof(argmax);
112 
113     if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0)
114         return argmax;
115     PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)");
116     return 0;
117 }
118 
119 
120 // Return 1 if pid refers to a zombie process else 0.
121 int
psutil_is_zombie(long pid)122 psutil_is_zombie(long pid) {
123     struct kinfo_proc kp;
124 
125     if (psutil_get_kinfo_proc(pid, &kp) == -1)
126         return 0;
127     return (kp.kp_proc.p_stat == SZOMB) ? 1 : 0;
128 }
129 
130 
131 
132 // return process args as a python list
133 PyObject *
psutil_get_cmdline(long pid)134 psutil_get_cmdline(long pid) {
135     int mib[3];
136     int nargs;
137     size_t len;
138     char *procargs = NULL;
139     char *arg_ptr;
140     char *arg_end;
141     char *curr_arg;
142     size_t argmax;
143 
144     PyObject *py_arg = NULL;
145     PyObject *py_retlist = NULL;
146 
147     // special case for PID 0 (kernel_task) where cmdline cannot be fetched
148     if (pid == 0)
149         return Py_BuildValue("[]");
150 
151     // read argmax and allocate memory for argument space.
152     argmax = psutil_get_argmax();
153     if (! argmax)
154         goto error;
155 
156     procargs = (char *)malloc(argmax);
157     if (NULL == procargs) {
158         PyErr_NoMemory();
159         goto error;
160     }
161 
162     // read argument space
163     mib[0] = CTL_KERN;
164     mib[1] = KERN_PROCARGS2;
165     mib[2] = (pid_t)pid;
166     if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
167         // In case of zombie process we'll get EINVAL. We translate it
168         // to NSP and _psosx.py will translate it to ZP.
169         if ((errno == EINVAL) && (psutil_pid_exists(pid)))
170             NoSuchProcess("");
171         else
172             PyErr_SetFromErrno(PyExc_OSError);
173         goto error;
174     }
175 
176     arg_end = &procargs[argmax];
177     // copy the number of arguments to nargs
178     memcpy(&nargs, procargs, sizeof(nargs));
179 
180     arg_ptr = procargs + sizeof(nargs);
181     len = strlen(arg_ptr);
182     arg_ptr += len + 1;
183 
184     if (arg_ptr == arg_end) {
185         free(procargs);
186         return Py_BuildValue("[]");
187     }
188 
189     // skip ahead to the first argument
190     for (; arg_ptr < arg_end; arg_ptr++) {
191         if (*arg_ptr != '\0')
192             break;
193     }
194 
195     // iterate through arguments
196     curr_arg = arg_ptr;
197     py_retlist = Py_BuildValue("[]");
198     if (!py_retlist)
199         goto error;
200     while (arg_ptr < arg_end && nargs > 0) {
201         if (*arg_ptr++ == '\0') {
202             py_arg = PyUnicode_DecodeFSDefault(curr_arg);
203             if (! py_arg)
204                 goto error;
205             if (PyList_Append(py_retlist, py_arg))
206                 goto error;
207             Py_DECREF(py_arg);
208             // iterate to next arg and decrement # of args
209             curr_arg = arg_ptr;
210             nargs--;
211         }
212     }
213 
214     free(procargs);
215     return py_retlist;
216 
217 error:
218     Py_XDECREF(py_arg);
219     Py_XDECREF(py_retlist);
220     if (procargs != NULL)
221         free(procargs);
222     return NULL;
223 }
224 
225 
226 // return process environment as a python string
227 PyObject *
psutil_get_environ(long pid)228 psutil_get_environ(long pid) {
229     int mib[3];
230     int nargs;
231     char *procargs = NULL;
232     char *procenv = NULL;
233     char *arg_ptr;
234     char *arg_end;
235     char *env_start;
236     size_t argmax;
237     PyObject *py_ret = NULL;
238 
239     // special case for PID 0 (kernel_task) where cmdline cannot be fetched
240     if (pid == 0)
241         goto empty;
242 
243     // read argmax and allocate memory for argument space.
244     argmax = psutil_get_argmax();
245     if (! argmax)
246         goto error;
247 
248     procargs = (char *)malloc(argmax);
249     if (NULL == procargs) {
250         PyErr_NoMemory();
251         goto error;
252     }
253 
254     // read argument space
255     mib[0] = CTL_KERN;
256     mib[1] = KERN_PROCARGS2;
257     mib[2] = (pid_t)pid;
258     if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
259         // In case of zombie process we'll get EINVAL. We translate it
260         // to NSP and _psosx.py will translate it to ZP.
261         if ((errno == EINVAL) && (psutil_pid_exists(pid)))
262             NoSuchProcess("");
263         else
264             PyErr_SetFromErrno(PyExc_OSError);
265         goto error;
266     }
267 
268     arg_end = &procargs[argmax];
269     // copy the number of arguments to nargs
270     memcpy(&nargs, procargs, sizeof(nargs));
271 
272     // skip executable path
273     arg_ptr = procargs + sizeof(nargs);
274     arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr);
275 
276     if (arg_ptr == NULL || arg_ptr == arg_end)
277         goto empty;
278 
279     // skip ahead to the first argument
280     for (; arg_ptr < arg_end; arg_ptr++) {
281         if (*arg_ptr != '\0')
282             break;
283     }
284 
285     // iterate through arguments
286     while (arg_ptr < arg_end && nargs > 0) {
287         if (*arg_ptr++ == '\0')
288             nargs--;
289     }
290 
291     // build an environment variable block
292     env_start = arg_ptr;
293 
294     procenv = calloc(1, arg_end - arg_ptr);
295     if (procenv == NULL) {
296         PyErr_NoMemory();
297         goto error;
298     }
299 
300     while (*arg_ptr != '\0' && arg_ptr < arg_end) {
301         char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr);
302 
303         if (s == NULL)
304             break;
305 
306         memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr);
307 
308         arg_ptr = s + 1;
309     }
310 
311     py_ret = PyUnicode_DecodeFSDefaultAndSize(
312         procenv, arg_ptr - env_start + 1);
313     if (!py_ret) {
314         // XXX: don't want to free() this as per:
315         // https://github.com/giampaolo/psutil/issues/926
316         // It sucks but not sure what else to do.
317         procargs = NULL;
318         goto error;
319     }
320 
321     free(procargs);
322     free(procenv);
323 
324     return py_ret;
325 
326 empty:
327     if (procargs != NULL)
328         free(procargs);
329     return Py_BuildValue("s", "");
330 
331 error:
332     Py_XDECREF(py_ret);
333     if (procargs != NULL)
334         free(procargs);
335     if (procenv != NULL)
336         free(procargs);
337     return NULL;
338 }
339 
340 
341 int
psutil_get_kinfo_proc(long pid,struct kinfo_proc * kp)342 psutil_get_kinfo_proc(long pid, struct kinfo_proc *kp) {
343     int mib[4];
344     size_t len;
345     mib[0] = CTL_KERN;
346     mib[1] = KERN_PROC;
347     mib[2] = KERN_PROC_PID;
348     mib[3] = (pid_t)pid;
349 
350     // fetch the info with sysctl()
351     len = sizeof(struct kinfo_proc);
352 
353     // now read the data from sysctl
354     if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) {
355         // raise an exception and throw errno as the error
356         PyErr_SetFromErrno(PyExc_OSError);
357         return -1;
358     }
359 
360     // sysctl succeeds but len is zero, happens when process has gone away
361     if (len == 0) {
362         NoSuchProcess("");
363         return -1;
364     }
365     return 0;
366 }
367 
368 
369 /*
370  * A wrapper around proc_pidinfo().
371  * Returns 0 on failure (and Python exception gets already set).
372  */
373 int
psutil_proc_pidinfo(long pid,int flavor,uint64_t arg,void * pti,int size)374 psutil_proc_pidinfo(long pid, int flavor, uint64_t arg, void *pti, int size) {
375     errno = 0;
376     int ret = proc_pidinfo((int)pid, flavor, arg, pti, size);
377     if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) {
378         psutil_raise_for_pid(pid, "proc_pidinfo()");
379         return 0;
380     }
381     return ret;
382 }
383