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