1 
2 #ifndef _GNU_SOURCE
3 #define _GNU_SOURCE 1
4 #endif
5 
6 #include <assert.h>
7 #include <errno.h>
8 #include <limits.h>  // for INT_MAX
9 #include <stdbool.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <signal.h>
13 #include <sys/sysctl.h>
14 #include <libproc.h>
15 #include <string.h>
16 
17 #include "process_info.h"
18 #include "../../common.h"
19 #include "../../posix.h"
20 
21 /*
22  * Returns a list of all BSD processes on the system.  This routine
23  * allocates the list and puts it in *procList and a count of the
24  * number of entries in *procCount.  You are responsible for freeing
25  * this list (use "free" from System framework).
26  * On success, the function returns 0.
27  * On error, the function returns a BSD errno value.
28  */
ps__get_proc_list(kinfo_proc ** procList,size_t * procCount)29 int ps__get_proc_list(kinfo_proc **procList, size_t *procCount) {
30   int mib3[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
31   size_t size, size2;
32   void *ptr;
33   int err;
34   int lim = 8;  // some limit
35 
36   assert( procList != NULL);
37   assert(*procList == NULL);
38   assert(procCount != NULL);
39 
40   *procCount = 0;
41 
42   /*
43    * We start by calling sysctl with ptr == NULL and size == 0.
44    * That will succeed, and set size to the appropriate length.
45    * We then allocate a buffer of at least that size and call
46    * sysctl with that buffer.  If that succeeds, we're done.
47    * If that call fails with ENOMEM, we throw the buffer away
48    * and try again.
49    * Note that the loop calls sysctl with NULL again.  This is
50    * is necessary because the ENOMEM failure case sets size to
51    * the amount of data returned, not the amount of data that
52    * could have been returned.
53    */
54   while (lim-- > 0) {
55     size = 0;
56     if (sysctl((int *)mib3, 3, NULL, &size, NULL, 0) == -1)
57       return errno;
58     size2 = size + (size >> 3);  // add some
59     if (size2 > size) {
60       ptr = malloc(size2);
61       if (ptr == NULL)
62 	ptr = malloc(size);
63       else
64 	size = size2;
65     }
66     else {
67       ptr = malloc(size);
68     }
69     if (ptr == NULL)
70       return ENOMEM;
71 
72     if (sysctl((int *)mib3, 3, ptr, &size, NULL, 0) == -1) {
73       err = errno;
74       free(ptr);
75       if (err != ENOMEM)
76 	return err;
77     }
78     else {
79       *procList = (kinfo_proc *)ptr;
80       *procCount = size / sizeof(kinfo_proc);
81       return 0;
82     }
83   }
84   return ENOMEM;
85 }
86 
87 
88 // Read the maximum argument size for processes
ps__get_argmax()89 int ps__get_argmax() {
90   int argmax;
91   int mib[] = { CTL_KERN, KERN_ARGMAX };
92   size_t size = sizeof(argmax);
93 
94   if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0)
95     return argmax;
96   return 0;
97 }
98 
99 // return process args as a character vector
ps__get_cmdline(long pid)100 SEXP ps__get_cmdline(long pid) {
101   int mib[3];
102   int nargs;
103   int idx;
104   size_t len;
105   char *procargs = NULL;
106   char *arg_ptr;
107   char *arg_end;
108   char *curr_arg;
109   size_t argmax;
110 
111   SEXP arg = R_NilValue;
112   SEXP retlist = R_NilValue;
113 
114   // special case for PID 0 (kernel_task) where cmdline cannot be fetched
115   if (pid == 0) {
116     ps__access_denied("");
117     return R_NilValue;
118   }
119 
120   // read argmax and allocate memory for argument space.
121   argmax = ps__get_argmax();
122   if (! argmax) {
123     ps__set_error_from_errno();
124     return R_NilValue;
125   }
126 
127   procargs = (char *) malloc(argmax);
128   if (NULL == procargs) {
129     ps__no_memory("");
130     return R_NilValue;
131   }
132 
133   PROTECT_PTR(procargs);
134 
135   // read argument space
136   mib[0] = CTL_KERN;
137   mib[1] = KERN_PROCARGS2;
138   mib[2] = (pid_t)pid;
139   if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
140     // In case of zombie process we'll get EINVAL, we fix this.
141     if (errno == EINVAL) errno = ESRCH;
142     UNPROTECT(1);
143     ps__set_error_from_errno();
144     return R_NilValue;
145   }
146 
147   arg_end = &procargs[argmax];
148   // copy the number of arguments to nargs
149   memcpy(&nargs, procargs, sizeof(nargs));
150 
151   arg_ptr = procargs + sizeof(nargs);
152   len = strlen(arg_ptr);
153   arg_ptr += len + 1;
154 
155   if (arg_ptr == arg_end) {
156     UNPROTECT(1);
157     return allocVector(STRSXP, 0);
158   }
159 
160   // skip ahead to the first argument
161   for (; arg_ptr < arg_end; arg_ptr++) {
162     if (*arg_ptr != '\0')
163       break;
164   }
165 
166   // iterate through arguments
167   curr_arg = arg_ptr;
168   idx = 0;
169   PROTECT(retlist = allocVector(STRSXP, nargs));
170   while (arg_ptr < arg_end && nargs > 0) {
171     if (*arg_ptr++ == '\0') {
172       PROTECT(arg = ps__str_to_utf8(curr_arg));
173       SET_STRING_ELT(retlist, idx++, STRING_ELT(arg, 0));
174       UNPROTECT(1);
175       // iterate to next arg and decrement # of args
176       curr_arg = arg_ptr;
177       nargs--;
178     }
179   }
180 
181   UNPROTECT(2);
182   return retlist;
183 }
184 
185 // return process environment as a character vector
ps__get_environ(long pid)186 SEXP ps__get_environ(long pid) {
187   int mib[3];
188   int nargs, nenv;
189   char *procargs = NULL;
190   char *arg_ptr;
191   char *arg_end;
192   char *env_start;
193   size_t argmax;
194   SEXP ret = NULL;
195 
196   // special case for PID 0 (kernel_task) where cmdline cannot be fetched
197   if (pid == 0) {
198     ps__access_denied("");
199     goto ret;
200   }
201 
202   // read argmax and allocate memory for argument space.
203   argmax = ps__get_argmax();
204   if (! argmax) {
205     ps__set_error_from_errno();
206     return R_NilValue;
207   }
208 
209   procargs = (char *) malloc(argmax);
210   if (NULL == procargs) {
211     ps__no_memory("");
212     return R_NilValue;
213   }
214 
215   PROTECT_PTR(procargs);
216 
217   // read argument space
218   mib[0] = CTL_KERN;
219   mib[1] = KERN_PROCARGS2;
220   mib[2] = (pid_t)pid;
221   if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) {
222     // In case of zombie process we'll get EINVAL, fix this.
223     if (errno == EINVAL) errno = ESRCH;
224     UNPROTECT(1);
225     ps__set_error_from_errno();
226     return R_NilValue;
227   }
228 
229   arg_end = &procargs[argmax];
230   // copy the number of arguments to nargs
231   memcpy(&nargs, procargs, sizeof(nargs));
232 
233   // skip executable path
234   arg_ptr = procargs + sizeof(nargs);
235   arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr);
236 
237   if (arg_ptr == NULL || arg_ptr == arg_end)
238     goto empty;
239 
240   // skip ahead to the first argument
241   for (; arg_ptr < arg_end; arg_ptr++) {
242     if (*arg_ptr != '\0')
243       break;
244   }
245 
246   // iterate through arguments
247   while (arg_ptr < arg_end && nargs > 0) {
248     if (*arg_ptr++ == '\0')
249       nargs--;
250   }
251 
252   // build an environment variable block
253   env_start = arg_ptr;
254 
255   /* Count the number of env vars first */
256   nenv = 0;
257   while (*arg_ptr != '\0' && arg_ptr < arg_end) {
258     char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr);
259 
260     if (s == NULL)
261       break;
262 
263     nenv++;
264     arg_ptr = s + 1;
265   }
266 
267   PROTECT(ret = allocVector(STRSXP, nenv));
268   arg_ptr = env_start;
269   nenv = 0;
270   while (*arg_ptr != '\0' && arg_ptr < arg_end) {
271     char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr);
272 
273     if (s == NULL)
274       break;
275 
276     SET_STRING_ELT(ret, nenv++, Rf_mkCharLen(arg_ptr, (int)(s - arg_ptr)));
277 
278     arg_ptr = s + 1;
279   }
280 
281   UNPROTECT(2);
282   return ret;
283 
284  empty:
285   UNPROTECT(1);
286 
287  ret:
288   return allocVector(STRSXP, 0);
289 }
290 
291 
ps__get_kinfo_proc(long pid,struct kinfo_proc * kp)292 int ps__get_kinfo_proc(long pid, struct kinfo_proc *kp) {
293   int mib[4];
294   size_t len;
295   mib[0] = CTL_KERN;
296   mib[1] = KERN_PROC;
297   mib[2] = KERN_PROC_PID;
298   mib[3] = (pid_t) pid;
299 
300   // fetch the info with sysctl()
301   len = sizeof(struct kinfo_proc);
302 
303   // now read the data from sysctl
304   if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) {
305     // raise an exception and throw errno as the error
306     ps__set_error_from_errno();
307     return -1;
308   }
309 
310   // sysctl succeeds but len is zero, happens when process has gone away
311   if (len == 0) {
312     ps__no_such_process(pid, 0);
313     return -1;
314   }
315   return 0;
316 }
317 
318 
319 /*
320  * A wrapper around proc_pidinfo().
321  * Returns 0 on failure (and error gets already set).
322  */
ps__proc_pidinfo(long pid,int flavor,uint64_t arg,void * pti,int size)323 int ps__proc_pidinfo(long pid, int flavor, uint64_t arg, void *pti, int size) {
324   errno = 0;
325   int ret = proc_pidinfo((int)pid, flavor, arg, pti, size);
326   if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) {
327     ps__raise_for_pid(pid, "proc_pidinfo()");
328     return 0;
329   }
330   return ret;
331 }
332