1/*
2 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#include "jni.h"
27#include "jni_util.h"
28#include "java_lang_ProcessHandleImpl.h"
29#include "java_lang_ProcessHandleImpl_Info.h"
30
31#include "ProcessHandleImpl_unix.h"
32
33#include <stdio.h>
34#include <errno.h>
35#include <signal.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <string.h>
39
40#include <sys/sysctl.h>
41
42/**
43 * Implementation of native ProcessHandleImpl functions for MAC OS X.
44 * See ProcessHandleImpl_unix.c for more details.
45 */
46
47void os_initNative(JNIEnv *env, jclass clazz) {}
48
49/*
50 * Returns the children of the requested pid and optionally each parent.
51 *
52 * Use sysctl to accumulate any process whose parent pid is zero or matches.
53 * The resulting pids are stored into the array of longs.
54 * The number of pids is returned if they all fit.
55 * If the parentArray is non-null, store the parent pid.
56 * If the array is too short, excess pids are not stored and
57 * the desired length is returned.
58 */
59jint os_getChildren(JNIEnv *env, jlong jpid, jlongArray jarray,
60                    jlongArray jparentArray, jlongArray jstimesArray) {
61    jlong* pids = NULL;
62    jlong* ppids = NULL;
63    jlong* stimes = NULL;
64    jsize parentArraySize = 0;
65    jsize arraySize = 0;
66    jsize stimesSize = 0;
67    jsize count = 0;
68    size_t bufSize = 0;
69    pid_t pid = (pid_t) jpid;
70
71    arraySize = (*env)->GetArrayLength(env, jarray);
72    JNU_CHECK_EXCEPTION_RETURN(env, -1);
73    if (jparentArray != NULL) {
74        parentArraySize = (*env)->GetArrayLength(env, jparentArray);
75        JNU_CHECK_EXCEPTION_RETURN(env, -1);
76
77        if (arraySize != parentArraySize) {
78            JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
79            return 0;
80        }
81    }
82    if (jstimesArray != NULL) {
83        stimesSize = (*env)->GetArrayLength(env, jstimesArray);
84        JNU_CHECK_EXCEPTION_RETURN(env, -1);
85
86        if (arraySize != stimesSize) {
87            JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
88            return 0;
89        }
90    }
91
92    // Get buffer size needed to read all processes
93    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
94    if (sysctl(mib, 4, NULL, &bufSize, NULL, 0) < 0) {
95        JNU_ThrowByNameWithLastError(env,
96            "java/lang/RuntimeException", "sysctl failed");
97        return -1;
98    }
99
100    // Allocate buffer big enough for all processes
101    void *buffer = malloc(bufSize);
102    if (buffer == NULL) {
103        JNU_ThrowOutOfMemoryError(env, "malloc failed");
104        return -1;
105    }
106
107    // Read process info for all processes
108    if (sysctl(mib, 4, buffer, &bufSize, NULL, 0) < 0) {
109        JNU_ThrowByNameWithLastError(env,
110            "java/lang/RuntimeException", "sysctl failed");
111        free(buffer);
112        return -1;
113    }
114
115    do { // Block to break out of on Exception
116        struct kinfo_proc *kp = (struct kinfo_proc *) buffer;
117        unsigned long nentries = bufSize / sizeof (struct kinfo_proc);
118        long i;
119
120        pids = (*env)->GetLongArrayElements(env, jarray, NULL);
121        if (pids == NULL) {
122            break;
123        }
124        if (jparentArray != NULL) {
125            ppids  = (*env)->GetLongArrayElements(env, jparentArray, NULL);
126            if (ppids == NULL) {
127                break;
128            }
129        }
130        if (jstimesArray != NULL) {
131            stimes  = (*env)->GetLongArrayElements(env, jstimesArray, NULL);
132            if (stimes == NULL) {
133                break;
134            }
135        }
136
137        // Process each entry in the buffer
138        for (i = nentries; --i >= 0; ++kp) {
139            if (pid == 0 || kp->kp_eproc.e_ppid == pid) {
140                if (count < arraySize) {
141                    // Only store if it fits
142                    pids[count] = (jlong) kp->kp_proc.p_pid;
143                    if (ppids != NULL) {
144                        // Store the parentPid
145                        ppids[count] = (jlong) kp->kp_eproc.e_ppid;
146                    }
147                    if (stimes != NULL) {
148                        // Store the process start time
149                        jlong startTime = kp->kp_proc.p_starttime.tv_sec * 1000 +
150                                          kp->kp_proc.p_starttime.tv_usec / 1000;
151                        stimes[count] = startTime;
152                    }
153                }
154                count++; // Count to tabulate size needed
155            }
156        }
157    } while (0);
158
159    if (pids != NULL) {
160        (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
161    }
162    if (ppids != NULL) {
163        (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
164    }
165    if (stimes != NULL) {
166        (*env)->ReleaseLongArrayElements(env, jstimesArray, stimes, 0);
167    }
168
169    free(buffer);
170    // If more pids than array had size for; count will be greater than array size
171    return count;
172}
173
174/**
175 * Use sysctl and return the ppid, total cputime and start time.
176 * Return: -1 is fail;  >=  0 is parent pid
177 * 'total' will contain the running time of 'pid' in nanoseconds.
178 * 'start' will contain the start time of 'pid' in milliseconds since epoch.
179 */
180pid_t os_getParentPidAndTimings(JNIEnv *env, pid_t jpid,
181                                jlong *totalTime, jlong *startTime) {
182
183    const pid_t pid = (pid_t) jpid;
184    pid_t ppid = -1;
185    struct kinfo_proc kp;
186    size_t bufSize = sizeof kp;
187
188    // Read the process info for the specific pid
189    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
190
191    if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) {
192        JNU_ThrowByNameWithLastError(env,
193            "java/lang/RuntimeException", "sysctl failed");
194        return -1;
195    }
196    if (bufSize > 0 && kp.kp_proc.p_pid == pid) {
197        *startTime = (jlong) (kp.kp_proc.p_starttime.tv_sec * 1000 +
198                              kp.kp_proc.p_starttime.tv_usec / 1000);
199        ppid = kp.kp_eproc.e_ppid;
200    }
201
202    // Get cputime if for current process
203    if (pid == getpid()) {
204        struct rusage usage;
205        if (getrusage(RUSAGE_SELF, &usage) == 0) {
206          jlong microsecs =
207              usage.ru_utime.tv_sec * 1000 * 1000 + usage.ru_utime.tv_usec +
208              usage.ru_stime.tv_sec * 1000 * 1000 + usage.ru_stime.tv_usec;
209          *totalTime = microsecs * 1000;
210        }
211    }
212
213    return ppid;
214
215}
216
217/**
218 * Return the uid of a process or -1 on error
219 */
220static uid_t getUID(pid_t pid) {
221    struct kinfo_proc kp;
222    size_t bufSize = sizeof kp;
223
224    // Read the process info for the specific pid
225    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
226
227    if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) == 0) {
228        if (bufSize > 0 && kp.kp_proc.p_pid == pid) {
229            return kp.kp_eproc.e_ucred.cr_uid;
230        }
231    }
232    return (uid_t)-1;
233}
234
235/**
236 * Retrieve the command and arguments for the process and store them
237 * into the Info object.
238 */
239void os_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
240    int mib[3], maxargs, nargs, i;
241    size_t size;
242    char *args, *cp, *sp, *np;
243
244    // Get the UID first. This is done here because it is cheap to do it here
245    // on other platforms like Linux/Solaris/AIX where the uid comes from the
246    // same source like the command line info.
247    unix_getUserInfo(env, jinfo, getUID(pid));
248
249    // Get the maximum size of the arguments
250    mib[0] = CTL_KERN;
251    mib[1] = KERN_ARGMAX;
252    size = sizeof(maxargs);
253    if (sysctl(mib, 2, &maxargs, &size, NULL, 0) == -1) {
254            JNU_ThrowByNameWithLastError(env,
255                "java/lang/RuntimeException", "sysctl failed");
256        return;
257    }
258
259    // Allocate an args buffer and get the arguments
260    args = (char *)malloc(maxargs);
261    if (args == NULL) {
262        JNU_ThrowOutOfMemoryError(env, "malloc failed");
263        return;
264    }
265
266    do {            // a block to break out of on error
267        char *argsEnd;
268        jstring cmdexe = NULL;
269
270        mib[0] = CTL_KERN;
271        mib[1] = KERN_PROCARGS2;
272        mib[2] = pid;
273        size = (size_t) maxargs;
274        if (sysctl(mib, 3, args, &size, NULL, 0) == -1) {
275            if (errno != EINVAL) {
276                JNU_ThrowByNameWithLastError(env,
277                    "java/lang/RuntimeException", "sysctl failed");
278            }
279            break;
280        }
281        memcpy(&nargs, args, sizeof(nargs));
282
283        cp = &args[sizeof(nargs)];      // Strings start after nargs
284        argsEnd = &args[size];
285
286        // Store the command executable path
287        if ((cmdexe = JNU_NewStringPlatform(env, cp)) == NULL) {
288            break;
289        }
290
291        // Skip trailing nulls after the executable path
292        for (cp = cp + strnlen(cp, argsEnd - cp); cp < argsEnd; cp++) {
293            if (*cp != '\0') {
294                break;
295            }
296        }
297
298        unix_fillArgArray(env, jinfo, nargs, cp, argsEnd, cmdexe, NULL);
299    } while (0);
300    // Free the arg buffer
301    free(args);
302}
303
304