blob: bc64cd1ae621e470326f173c36e71b35e903da39 [file] [log] [blame]
/*
* 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 <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sysctl.h>
/**
* 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);
}