jdk/src/java.base/macosx/native/libjava/ProcessHandleImpl_macosx.c
author simonis
Fri, 14 Aug 2015 10:35:45 +0200
changeset 32209 24bb680a1609
parent 31682 c19dcf5e0b6d
permissions -rw-r--r--
8131168: Refactor ProcessHandleImpl_*.c and add implememtation for AIX Reviewed-by: rriggs, smarks

/*
 * 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);
}