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