1 /*
2  * swrun_darwin.c:
3  *     hrSWRunTable data access:
4  *     Darwin
5  */
6 /*
7  * Copyright (C) 2007 Apple, Inc. All rights reserved.
8  * Use is subject to license terms specified in the COPYING file
9  * distributed with the Net-SNMP package.
10  */
11 #include <net-snmp/net-snmp-config.h>
12 #include <net-snmp/net-snmp-includes.h>
13 #include <net-snmp/agent/net-snmp-agent-includes.h>
14 #include <net-snmp/library/container.h>
15 #include <net-snmp/library/snmp_debug.h>
16 #include <net-snmp/data_access/swrun.h>
17 #include "swrun_private.h"
18 
19 #include <stdlib.h>
20 #include <unistd.h>
21 
22 #include <libproc.h>
23 #include <sys/proc_info.h>
24 #include <sys/sysctl.h>	/* for sysctl() and struct kinfo_proc */
25 
26 #define __APPLE_API_EVOLVING 1
27 #include <sys/acl.h> /* or else CoreFoundation.h barfs */
28 #undef __APPLE_API_EVOLVING
29 
30 #include <CoreFoundation/CFBase.h>
31 #include <CoreFoundation/CFNumber.h>
32 #include <CoreFoundation/CFBundle.h>
33 #include <CoreServices/CoreServices.h>
34 #include <IOKit/IOCFBundle.h>
35 #include <mach/mach.h>
36 #include <mach/mach_time.h>
37 
38 /** sigh... can't find Processes.h */
39 #ifndef kProcessDictionaryIncludeAllInformationMask
40 #define kProcessDictionaryIncludeAllInformationMask (long)0xFFFFFFFF
41 #endif
42 #ifndef procNotFound
43 #define procNotFound -600
44 #endif
45 
46 /* ---------------------------------------------------------------------
47  */
48 static int _kern_argmax;
49 static int _set_command_name(netsnmp_swrun_entry *entry);
50 
51 /** avoid kernel bug in 10.2. 8192 oughta be enough anyways, right? */
52 #define MAX_KERN_ARGMAX 8192
53 
54 /* ---------------------------------------------------------------------
55  */
56 void
netsnmp_arch_swrun_init(void)57 netsnmp_arch_swrun_init(void)
58 {
59     int    mib[2] = { CTL_KERN, KERN_ARGMAX };
60     size_t size, mib_size = sizeof(mib)/sizeof(mib[0]);
61 
62     DEBUGMSGTL(("swrun:load:arch","init\n"));
63 
64     size = sizeof(_kern_argmax);
65     if (sysctl(mib, mib_size, &_kern_argmax, &size, NULL, 0) == -1) {
66         snmp_log(LOG_ERR, "Error in ARGMAX sysctl(): %s", strerror(errno));
67         _kern_argmax = MAX_KERN_ARGMAX;
68     }
69     else if (_kern_argmax > MAX_KERN_ARGMAX) {
70         DEBUGMSGTL(("swrun:load:arch",
71                     "artificially limiting ARGMAX to %d (from %d)\n",
72                     MAX_KERN_ARGMAX, _kern_argmax));
73         _kern_argmax = MAX_KERN_ARGMAX;
74     }
75 
76 
77 }
78 
79 /* ---------------------------------------------------------------------
80  */
81 #define SWRUNINDENT "           "
82 int
netsnmp_arch_swrun_container_load(netsnmp_container * container,u_int flags)83 netsnmp_arch_swrun_container_load( netsnmp_container *container, u_int flags)
84 {
85     int	                 mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL};
86     size_t               buf_size, mib_size = sizeof(mib)/sizeof(mib[0]);
87     struct kinfo_proc   *processes = NULL;
88     struct proc_taskallinfo taskinfo;
89     netsnmp_swrun_entry *entry;
90     int                  rc, num_entries, i;
91 
92     DEBUGMSGTL(("swrun:load:arch"," load\n"));
93 
94     /*
95      * get size to allocate. This introduces a bit of a race condition,
96      * as the size could change between this call and the next...
97      */
98     rc = sysctl(mib, mib_size, NULL, &buf_size, NULL, 0);
99     if (rc < 0) {
100         snmp_log(LOG_ERR, "KERN_PROC_ALL size sysctl failed: %d\n", rc);
101         return -1;
102     }
103 
104     processes = (struct kinfo_proc*) malloc(buf_size);
105     if (NULL == processes) {
106         snmp_log(LOG_ERR, "malloc failed\n");
107         return -1;
108     }
109 
110     rc = sysctl(mib, mib_size, processes, &buf_size, NULL, 0);
111     if (rc < 0) {
112         snmp_log(LOG_ERR, "KERN_PROC_ALL sysctl failed: %d\n", rc);
113         free(processes);
114         return -1;
115     }
116 
117     num_entries = buf_size / sizeof(struct kinfo_proc);
118 
119     for (i = 0; i < num_entries; i++) {
120         /*
121          * skip empty names.
122          * p_stat = (SIDL|SRUN|SSLEEP|SSTOP|SZOMB)
123          */
124         if (('\0' == processes[i].kp_proc.p_comm[0]) ||
125             (0 == processes[i].kp_proc.p_pid)) {
126             DEBUGMSGTL(("swrun:load:arch",
127                         " skipping p_comm '%s', pid %5d, p_pstat %d\n",
128                         processes[i].kp_proc.p_comm ?
129                         processes[i].kp_proc.p_comm : "NULL",
130                         processes[i].kp_proc.p_pid,
131                         processes[i].kp_proc.p_stat));
132             continue;
133         }
134 
135         DEBUGMSGTL(("swrun:load:arch"," %s pid %5d\n",
136                     processes[i].kp_proc.p_comm,
137                     processes[i].kp_proc.p_pid));
138 
139         entry = netsnmp_swrun_entry_create(processes[i].kp_proc.p_pid);
140         if (NULL == entry)
141             continue; /* error already logged by function */
142         rc = CONTAINER_INSERT(container, entry);
143 
144         /*
145          * p_comm is a partial name, but it is all we have at this point.
146          */
147         entry->hrSWRunName_len = snprintf(entry->hrSWRunName,
148                                           sizeof(entry->hrSWRunName)-1,
149                                           "%s", processes[i].kp_proc.p_comm);
150 
151         /** sysctl for name, path, params */
152         rc = _set_command_name(entry);
153 
154         /*
155          * map p_stat to RunStatus. Odd that there is no 'running' status.
156          */
157         switch(processes[i].kp_proc.p_stat) {
158             case SRUN:
159                 entry->hrSWRunStatus = HRSWRUNSTATUS_RUNNABLE;
160                 break;
161             case SSLEEP:
162             case SSTOP:
163                 entry->hrSWRunStatus = HRSWRUNSTATUS_NOTRUNNABLE;
164                 break;
165             case SIDL:
166             case SZOMB:
167             default:
168                 entry->hrSWRunStatus = HRSWRUNSTATUS_INVALID;
169                 break;
170         }
171 
172         /*
173          * check for system processes
174          */
175         if (P_SYSTEM & processes[i].kp_proc.p_flag) {
176             entry->hrSWRunType = HRSWRUNTYPE_OPERATINGSYSTEM;
177             DEBUGMSGTL(("swrun:load:arch", SWRUNINDENT "SYSTEM\n"));
178         }
179         else entry->hrSWRunType = HRSWRUNTYPE_APPLICATION;
180 
181         /*
182          * get mem size, run time
183          */
184         rc = proc_pidinfo( processes[i].kp_proc.p_pid, PROC_PIDTASKALLINFO, 0,
185                            &taskinfo, sizeof(taskinfo));
186         if (sizeof(taskinfo) != rc) {
187             DEBUGMSGTL(("swrun:load:arch", " proc_pidinfo returned %d\n", rc));
188         }
189         else {
190             uint64_t task_mem = taskinfo.ptinfo.pti_resident_size / 1024;
191             union {
192                 u_quad_t     uq; /* u_int64_t */
193                 UnsignedWide uw; /* struct u_int32_t hi/lo */
194             } at, ns;
195             at.uq = taskinfo.ptinfo.pti_total_user +
196                     taskinfo.ptinfo.pti_total_system;
197             ns = at;
198             ns.uq = ns.uq / 10000000LL; /* nano to deci */
199             if (task_mem > INT32_MAX) {
200                 DEBUGMSGTL(("swrun:load:arch", SWRUNINDENT "mem overflow\n"));
201                 task_mem = INT32_MAX;
202             }
203             if (ns.uq > INT32_MAX) {
204                 DEBUGMSGTL(("swrun:load:arch", SWRUNINDENT "time overflow\n"));
205                 ns.uq = INT32_MAX;
206             }
207             entry->hrSWRunPerfMem = task_mem;
208             entry->hrSWRunPerfCPU = ns.uq;
209         }
210     }
211     free(processes);
212 
213     DEBUGMSGTL(("swrun:load:arch"," loaded %d entries\n",
214                 (int)CONTAINER_SIZE(container)));
215 
216     return 0;
217 }
218 
219 /* ---------------------------------------------------------------------
220  * The following code was snagged from Darwin code, and the original
221  * file had the following licences:
222  */
223 
224 /*
225  * Copyright (c) 2002-2004 Apple Computer, Inc.  All rights reserved.
226  *
227  * @APPLE_LICENSE_HEADER_START@
228  *
229  * The contents of this file constitute Original Code as defined in and
230  * are subject to the Apple Public Source License Version 1.1 (the
231  * "License").  You may not use this file except in compliance with the
232  * License.  Please obtain a copy of the License at
233  * http://www.apple.com/publicsource and read it before using this file.
234  *
235  * This Original Code and all software distributed under the License are
236  * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
237  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
238  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
239  * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
240  * License for the specific language governing rights and limitations
241  * under the License.
242  *
243  * @APPLE_LICENSE_HEADER_END@
244  */
245 #ifdef JAGUAR /* xxx configure test? */
246 static int
_set_command_name_jaguar(netsnmp_swrun_entry * entry)247 _set_command_name_jaguar(netsnmp_swrun_entry *entry)
248 {
249     int	        mib[3] = {CTL_KERN, KERN_PROCARGS, 0};
250     size_t      procargssize, mib_size = sizeof(mib)/sizeof(mib[0]);
251     char       *arg_end, *exec_path;
252     int        *ip;
253     int         len;
254     char       *command_beg, *command, *command_end;
255     char        arg_buf[MAX_KERN_ARGMAX]; /* max to avoid kernel bug */
256 
257     DEBUGMSGTL(("swrun:load:arch:_cn"," pid %d\n", entry->hrSWRunIndex));
258 
259     mib[2] = entry->hrSWRunIndex;
260 
261     memset(arg_buf, 0x0, sizeof(arg_buf));
262     procargssize = _kern_argmax;
263     if (sysctl(mib, mib_size, arg_buf, &procargssize, NULL, 0) == -1) {
264         snmp_log(LOG_ERR, "Error in PROCARGS sysctl() for %s: %s\n",
265                  entry->hrSWRunName, strerror(errno));
266         entry->hrSWRunPath_len = 0;
267         return -1;
268     }
269 
270     /* Set ip just above the end of arg_buf. */
271     arg_end = &arg_buf[procargssize];
272     ip = (int *)arg_end;
273 
274     /*
275      * Skip the last 2 words, since the last is a 0 word, and
276      * the second to last may be as well, if there are no
277      * arguments.
278      */
279     ip -= 3;
280 
281     /* Iterate down the arguments until a 0 word is found. */
282     for (; *ip != 0; ip--) {
283         if (ip == (int *)arg_buf) {
284             DEBUGMSGTL(("swrun:load:arch:_cn"," unexpected toparg\n"));
285             return -1;
286         }
287     }
288 
289     /* The saved exec_path is just above the 0 word. */
290     ip++;
291     exec_path = (char *)ip;
292     DEBUGMSGTL(("swrun:load:arch:_cn"," exec_path %s\n", exec_path));
293     len = strlen(exec_path);
294     strlcpy(entry->hrSWRunPath, exec_path, sizeof(entry->hrSWRunPath));
295     if (len > sizeof(entry->hrSWRunPath)-1) {
296         DEBUGMSGTL(("swrun:load:arch:_cn"," truncating long run path\n"));
297         entry->hrSWRunPath[sizeof(entry->hrSWRunPath)-2] = '$';
298         entry->hrSWRunPath_len = sizeof(entry->hrSWRunPath)-1;
299         DEBUGMSGTL(("swrun:load:arch:_cn"," exec_path %s\n",
300                     entry->hrSWRunPath));
301     }
302     else
303         entry->hrSWRunPath_len = len;
304 
305     /*
306      * Get the beginning of the first argument.  It is word-aligned,
307      * so skip padding '\0' bytes.
308      */
309     command_beg = exec_path + strlen(exec_path);
310     DEBUGMSGTL(("swrun:load:arch:_cn"," command_beg '%s'\n", command_beg));
311     for (; *command_beg == '\0'; command_beg++) {
312         if (command_beg >= arg_end)
313             return -1;
314     }
315     DEBUGMSGTL(("swrun:load:arch:_cn"," command_beg '%s'\n", command_beg));
316 
317     /* Get the basename of command. */
318     command = command_end = command_beg + strlen(command_beg) + 1;
319     for (command--; command >= command_beg; command--) {
320         if (*command == '/')
321             break;
322     }
323     command++;
324     DEBUGMSGTL(("swrun:load:arch:_cn"," command '%s'\n", command));
325 
326     /* Allocate space for the command and copy. */
327     DEBUGMSGTL(("swrun:load:arch:_cn",
328                 SWRUNINDENT "kernel name %s\n", command));
329     if (strncmp(command, entry->hrSWRunName, sizeof(entry->hrSWRunName)-1)) {
330         strlcpy(entry->hrSWRunName, command, sizeof(entry->hrSWRunName));
331         entry->hrSWRunName_len = strlen(entry->hrSWRunName);
332         DEBUGMSGTL(("swrun:load:arch:_cn", "**"
333                     SWRUNINDENT "updated name to %s\n", entry->hrSWRunName));
334         return 0;
335     }
336 
337     /** no error, no change */
338     return 1;
339 }
340 #else
341 static int
_set_command_name(netsnmp_swrun_entry * entry)342 _set_command_name(netsnmp_swrun_entry *entry)
343 {
344     int	        mib[3] = {CTL_KERN, 0, 0};
345     size_t      procargssize, mib_size = sizeof(mib)/sizeof(mib[0]);
346     char       *cp;
347     int         len, nargs;
348     char       *command_beg, *command, *command_end, *exec_path, *argN;
349     char        arg_buf[MAX_KERN_ARGMAX]; /* max to avoid kernel bug */
350 
351     /*
352      * arguments
353      */
354     mib[1] = KERN_PROCARGS2;
355     mib[2] = entry->hrSWRunIndex;
356 
357     memset(arg_buf, 0x0, sizeof(arg_buf));
358     procargssize = _kern_argmax;
359     if (sysctl(mib, mib_size, arg_buf, &procargssize, NULL, 0) == -1) {
360         snmp_log(LOG_ERR, "Error in PROCARGS2 sysctl() for %s: %s\n",
361                  entry->hrSWRunName, strerror(errno));
362         entry->hrSWRunPath_len = 0;
363         entry->hrSWRunParameters_len = 0;
364         return -1;
365     }
366     else {
367         memcpy(&nargs,arg_buf, sizeof(nargs));
368     }
369 
370     exec_path = arg_buf + sizeof(nargs);
371     len = strlen(exec_path);
372     strlcpy(entry->hrSWRunPath, exec_path, sizeof(entry->hrSWRunPath));
373     if (len > sizeof(entry->hrSWRunPath)-1) {
374         DEBUGMSGTL(("swrun:load:arch:_cn"," truncating long run path\n"));
375         entry->hrSWRunPath[sizeof(entry->hrSWRunPath)-2] = '$';
376         entry->hrSWRunPath_len = sizeof(entry->hrSWRunPath)-1;
377     }
378     else
379         entry->hrSWRunPath_len = len;
380 
381     /** Skip the saved exec_path. */
382 #if 0
383     cp = exec_path + len;
384 #else
385     for (cp = exec_path; cp < &arg_buf[procargssize]; cp++) {
386         if (*cp == '\0')
387             break; /* End of exec_path reached. */
388     }
389     if (cp != exec_path + len) {
390         DEBUGMSGTL(("swrun:load:arch:_cn", " OFF BY %d\n",
391                     (int)((exec_path + len) - cp)));
392         netsnmp_assert( cp == exec_path + len );
393     }
394 #endif
395     if (cp == &arg_buf[procargssize]) {
396         DEBUGMSGTL(("swrun:load:arch:_cn"," unexpected end of buffer\n"));
397         return -1;
398     }
399 
400     /** Skip trailing '\0' characters. */
401     for (; cp < &arg_buf[procargssize]; cp++) {
402         if (*cp != '\0')
403             break; /* Beginning of first argument reached. */
404     }
405     if (cp == &arg_buf[procargssize]) {
406         DEBUGMSGTL(("swrun:load:arch:_cn"," unexpected end of buffer\n"));
407         return -1;
408     }
409     command_beg = cp;
410 
411     /*
412      * Make sure that the command is '\0'-terminated.  This protects
413      * against malicious programs; under normal operation this never
414      * ends up being a problem..
415      */
416     for (; cp < &arg_buf[procargssize]; cp++) {
417         if (*cp == '\0')
418             break; /* End of first argument reached. */
419     }
420     if (cp == &arg_buf[procargssize]) {
421         DEBUGMSGTL(("swrun:load:arch:_cn"," unexpected end of buffer\n"));
422         return -1;
423     }
424     command_end = command = cp;
425     --nargs;
426 
427     /*
428      * save arguments
429      */
430     while( nargs && cp < &arg_buf[procargssize] ) {
431         /** Skip trailing '\0' characters from prev arg. */
432         for (; (cp < &arg_buf[procargssize]) && (*cp == 0); cp++)
433             ; /* noop */
434         if (cp == &arg_buf[procargssize])
435             continue; /* effectively a break */
436 
437         /** save argN start */
438         argN = cp;
439         --nargs;
440         if (0 == nargs)
441             continue; /* effectively a break */
442 
443         /** Skip to end of arg */
444         for (; (cp < &arg_buf[procargssize]) && (*cp != 0); cp++)
445             ;  /* noop */
446         if (cp == &arg_buf[procargssize])
447             continue; /* effectively a break */
448 
449         /*
450          * check for overrun into env
451          */
452         if ((*argN != '-') && strchr(argN,'='))  {
453             DEBUGMSGTL(("swrun:load:arch:_cn", " *** OVERRUN INTO ENV %d\n",nargs));
454             continue;
455         }
456 
457         /*
458          * save arg
459          */
460         if(entry->hrSWRunParameters_len < sizeof(entry->hrSWRunParameters)-1) {
461             strlcat(&entry->hrSWRunParameters[entry->hrSWRunParameters_len],
462                     argN, sizeof(entry->hrSWRunParameters)-entry->hrSWRunParameters_len-1);
463             entry->hrSWRunParameters_len = strlen(entry->hrSWRunParameters);
464             if ((entry->hrSWRunParameters_len+2 < sizeof(entry->hrSWRunParameters)-1) && (0 != nargs)) {
465                 /* add space between params */
466                 entry->hrSWRunParameters[entry->hrSWRunParameters_len++] = ' ';
467                 entry->hrSWRunParameters[entry->hrSWRunParameters_len] = 0;
468             } else {
469                 DEBUGMSGTL(("swrun:load:arch:_cn"," truncating long arg list\n"));
470                 entry->hrSWRunParameters[entry->hrSWRunParameters_len++] = '$';
471                 entry->hrSWRunParameters[entry->hrSWRunParameters_len] = '0';
472             }
473         }
474     }
475     if (' ' == entry->hrSWRunParameters[entry->hrSWRunParameters_len])
476         entry->hrSWRunParameters[entry->hrSWRunParameters_len--] = 0;
477 
478 
479     /* Get the basename of command. */
480     for (command--; command >= command_beg; command--) {
481         if (*command == '/')
482             break;
483     }
484     command++;
485 
486     /* Allocate space for the command and copy. */
487     if (strncmp(command, entry->hrSWRunName, sizeof(entry->hrSWRunName)-1)) {
488         strlcpy(entry->hrSWRunName, command, sizeof(entry->hrSWRunName));
489         entry->hrSWRunName_len = strlen(entry->hrSWRunName);
490         DEBUGMSGTL(("swrun:load:arch:_cn",
491                     " **updated name to %s\n", entry->hrSWRunName));
492     }
493 
494     return 0;
495 }
496 #endif
497