/* * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "jni.h" #include "jni_util.h" #include "java_lang_ProcessHandleImpl.h" #include "java_lang_ProcessHandleImpl_Info.h" #include "ProcessHandleImpl_unix.h" #include #include #include #include #include #include #include /** * Implementation of native ProcessHandleImpl functions for MAC OS X. * See ProcessHandleImpl_unix.c for more details. */ void os_initNative(JNIEnv *env, jclass clazz) {} /* * Returns the children of the requested pid and optionally each parent. * * Use sysctl to accumulate any process whose parent pid is zero or matches. * The resulting pids are stored into the array of longs. * The number of pids is returned if they all fit. * If the parentArray is non-null, store the parent pid. * If the array is too short, excess pids are not stored and * the desired length is returned. */ jint os_getChildren(JNIEnv *env, jlong jpid, jlongArray jarray, jlongArray jparentArray, jlongArray jstimesArray) { jlong* pids = NULL; jlong* ppids = NULL; jlong* stimes = NULL; jsize parentArraySize = 0; jsize arraySize = 0; jsize stimesSize = 0; jsize count = 0; size_t bufSize = 0; pid_t pid = (pid_t) jpid; arraySize = (*env)->GetArrayLength(env, jarray); JNU_CHECK_EXCEPTION_RETURN(env, -1); if (jparentArray != NULL) { parentArraySize = (*env)->GetArrayLength(env, jparentArray); JNU_CHECK_EXCEPTION_RETURN(env, -1); if (arraySize != parentArraySize) { JNU_ThrowIllegalArgumentException(env, "array sizes not equal"); return 0; } } if (jstimesArray != NULL) { stimesSize = (*env)->GetArrayLength(env, jstimesArray); JNU_CHECK_EXCEPTION_RETURN(env, -1); if (arraySize != stimesSize) { JNU_ThrowIllegalArgumentException(env, "array sizes not equal"); return 0; } } // Get buffer size needed to read all processes int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; if (sysctl(mib, 4, NULL, &bufSize, NULL, 0) < 0) { JNU_ThrowByNameWithLastError(env, "java/lang/RuntimeException", "sysctl failed"); return -1; } // Allocate buffer big enough for all processes void *buffer = malloc(bufSize); if (buffer == NULL) { JNU_ThrowOutOfMemoryError(env, "malloc failed"); return -1; } // Read process info for all processes if (sysctl(mib, 4, buffer, &bufSize, NULL, 0) < 0) { JNU_ThrowByNameWithLastError(env, "java/lang/RuntimeException", "sysctl failed"); free(buffer); return -1; } do { // Block to break out of on Exception struct kinfo_proc *kp = (struct kinfo_proc *) buffer; unsigned long nentries = bufSize / sizeof (struct kinfo_proc); long i; pids = (*env)->GetLongArrayElements(env, jarray, NULL); if (pids == NULL) { break; } if (jparentArray != NULL) { ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL); if (ppids == NULL) { break; } } if (jstimesArray != NULL) { stimes = (*env)->GetLongArrayElements(env, jstimesArray, NULL); if (stimes == NULL) { break; } } // Process each entry in the buffer for (i = nentries; --i >= 0; ++kp) { if (pid == 0 || kp->kp_eproc.e_ppid == pid) { if (count < arraySize) { // Only store if it fits pids[count] = (jlong) kp->kp_proc.p_pid; if (ppids != NULL) { // Store the parentPid ppids[count] = (jlong) kp->kp_eproc.e_ppid; } if (stimes != NULL) { // Store the process start time jlong startTime = kp->kp_proc.p_starttime.tv_sec * 1000 + kp->kp_proc.p_starttime.tv_usec / 1000; stimes[count] = startTime; } } count++; // Count to tabulate size needed } } } while (0); if (pids != NULL) { (*env)->ReleaseLongArrayElements(env, jarray, pids, 0); } if (ppids != NULL) { (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0); } if (stimes != NULL) { (*env)->ReleaseLongArrayElements(env, jstimesArray, stimes, 0); } free(buffer); // If more pids than array had size for; count will be greater than array size return count; } /** * Use sysctl and return the ppid, total cputime and start time. * Return: -1 is fail; >= 0 is parent pid * 'total' will contain the running time of 'pid' in nanoseconds. * 'start' will contain the start time of 'pid' in milliseconds since epoch. */ pid_t os_getParentPidAndTimings(JNIEnv *env, pid_t jpid, jlong *totalTime, jlong *startTime) { const pid_t pid = (pid_t) jpid; pid_t ppid = -1; struct kinfo_proc kp; size_t bufSize = sizeof kp; // Read the process info for the specific pid int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) { JNU_ThrowByNameWithLastError(env, "java/lang/RuntimeException", "sysctl failed"); return -1; } if (bufSize > 0 && kp.kp_proc.p_pid == pid) { *startTime = (jlong) (kp.kp_proc.p_starttime.tv_sec * 1000 + kp.kp_proc.p_starttime.tv_usec / 1000); ppid = kp.kp_eproc.e_ppid; } // Get cputime if for current process if (pid == getpid()) { struct rusage usage; if (getrusage(RUSAGE_SELF, &usage) == 0) { jlong microsecs = usage.ru_utime.tv_sec * 1000 * 1000 + usage.ru_utime.tv_usec + usage.ru_stime.tv_sec * 1000 * 1000 + usage.ru_stime.tv_usec; *totalTime = microsecs * 1000; } } return ppid; } /** * Return the uid of a process or -1 on error */ static uid_t getUID(pid_t pid) { struct kinfo_proc kp; size_t bufSize = sizeof kp; // Read the process info for the specific pid int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) == 0) { if (bufSize > 0 && kp.kp_proc.p_pid == pid) { return kp.kp_eproc.e_ucred.cr_uid; } } return (uid_t)-1; } /** * Retrieve the command and arguments for the process and store them * into the Info object. */ void os_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) { int mib[3], maxargs, nargs, i; size_t size; char *args, *cp, *sp, *np; // Get the UID first. This is done here because it is cheap to do it here // on other platforms like Linux/Solaris/AIX where the uid comes from the // same source like the command line info. unix_getUserInfo(env, jinfo, getUID(pid)); // Get the maximum size of the arguments mib[0] = CTL_KERN; mib[1] = KERN_ARGMAX; size = sizeof(maxargs); if (sysctl(mib, 2, &maxargs, &size, NULL, 0) == -1) { JNU_ThrowByNameWithLastError(env, "java/lang/RuntimeException", "sysctl failed"); return; } // Allocate an args buffer and get the arguments args = (char *)malloc(maxargs); if (args == NULL) { JNU_ThrowOutOfMemoryError(env, "malloc failed"); return; } do { // a block to break out of on error char *argsEnd; jstring cmdexe = NULL; mib[0] = CTL_KERN; mib[1] = KERN_PROCARGS2; mib[2] = pid; size = (size_t) maxargs; if (sysctl(mib, 3, args, &size, NULL, 0) == -1) { if (errno != EINVAL) { JNU_ThrowByNameWithLastError(env, "java/lang/RuntimeException", "sysctl failed"); } break; } memcpy(&nargs, args, sizeof(nargs)); cp = &args[sizeof(nargs)]; // Strings start after nargs argsEnd = &args[size]; // Store the command executable path if ((cmdexe = JNU_NewStringPlatform(env, cp)) == NULL) { break; } // Skip trailing nulls after the executable path for (cp = cp + strnlen(cp, argsEnd - cp); cp < argsEnd; cp++) { if (*cp != '\0') { break; } } unix_fillArgArray(env, jinfo, nargs, cp, argsEnd, cmdexe, NULL); } while (0); // Free the arg buffer free(args); }