jdk/src/java.base/solaris/native/libjava/ProcessHandleImpl_solaris.c
author rriggs
Fri, 29 May 2015 14:04:12 -0400
changeset 30899 d2408e757489
child 31682 c19dcf5e0b6d
permissions -rw-r--r--
8077350: JEP 102 Process API Updates Implementation Reviewed-by: chegar, plevart, psandoz, darcy, martin, alanb

/*
 * 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 <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <procfs.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>

/**
 * Implementations of ProcessHandleImpl functions that are
 * NOT common to all Unix variants:
 * - getProcessPids0(pid, pidArray)
 *
 * Implementations of ProcessHandleImpl_Info
 * - totalTime, startTime
 * - Command
 * - Arguments
 */

/*
 * Signatures for internal OS specific functions.
 */
static pid_t parentPid(JNIEnv *env, pid_t pid);
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);

extern jstring uidToUser(JNIEnv* env, uid_t uid);

/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_commandID;

/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;

/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;

/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;

/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_userID;

/* static value for clock ticks per second. */
static long clock_ticks_per_second;

/**************************************************************
 * Static method to initialize field IDs and the ticks per second rate.
 *
 * Class:     java_lang_ProcessHandleImpl_Info
 * Method:    initIDs
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
  (JNIEnv *env, jclass clazz) {

    CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
        clazz, "command", "Ljava/lang/String;"));
    CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
        clazz, "arguments", "[Ljava/lang/String;"));
    CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
        clazz, "totalTime", "J"));
    CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
        clazz, "startTime", "J"));
    CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
        clazz, "user", "Ljava/lang/String;"));
    clock_ticks_per_second = sysconf(_SC_CLK_TCK);
}

/*
 * Returns the parent pid of the requested pid.
 *
 * Class:     java_lang_ProcessHandleImpl
 * Method:    parent0
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
(JNIEnv *env, jobject obj, jlong jpid) {
    pid_t pid = (pid_t) jpid;
    pid_t ppid = -1;

    if (pid == getpid()) {
        ppid = getppid();
    } else {
        ppid = parentPid(env, pid);
    }
    return (jlong) ppid;
}

/*
 * Returns the children of the requested pid and optionally each parent.
 *
 * Class:     java_lang_ProcessHandleImpl
 * Method:    getChildPids
 * Signature: (J[J)I
 *
 * Reads /proc and accumulates any process who parent pid matches.
 * The resulting pids are stored into the array of longs.
 * The number of pids is returned if they all fit.
 * If the array is too short, the desired length is returned.
 */
JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
(JNIEnv *env, jclass clazz, jlong jpid,
    jlongArray jarray, jlongArray jparentArray)
{
    DIR* dir;
    struct dirent* ptr;
    pid_t pid = (pid_t) jpid;
    size_t count = 0;
    jlong* pids = NULL;
    jlong* ppids = NULL;
    size_t parentArraySize = 0;
    size_t arraySize = 0;

    arraySize = (*env)->GetArrayLength(env, jarray);
    JNU_CHECK_EXCEPTION_RETURN(env, 0);
    if (jparentArray != NULL) {
        parentArraySize = (*env)->GetArrayLength(env, jparentArray);
        JNU_CHECK_EXCEPTION_RETURN(env, 0);

        if (arraySize != parentArraySize) {
            JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
            return 0;
        }
    }

    /*
     * To locate the children we scan /proc looking for files that have a
     * positive integer as a filename.
     */
    if ((dir = opendir("/proc")) == NULL) {
        JNU_ThrowByNameWithLastError(env,
            "java/lang/Runtime", "Unable to open /proc");
        return 0;
    }

    do { // Block to break out of on Exception
        pids = (*env)->GetLongArrayElements(env, jarray, NULL);
        if (pids == NULL) {
            break;
        }
        if (jparentArray != NULL) {
            ppids  = (*env)->GetLongArrayElements(env, jparentArray, NULL);
            if (ppids == NULL) {
                break;
            }
        }

        while ((ptr = readdir(dir)) != NULL) {
            pid_t ppid;

            /* skip files that aren't numbers */
            pid_t childpid = (pid_t) atoi(ptr->d_name);
            if ((int) childpid <= 0) {
                continue;
            }

            ppid = 0;
            if (pid != 0 || jparentArray != NULL) {
                // parentPid opens and reads /proc/pid/stat
                ppid = parentPid(env, childpid);
            }
            if (pid == 0 || ppid == pid) {
                if (count < arraySize) {
                    // Only store if it fits
                    pids[count] = (jlong) childpid;

                    if (ppids != NULL) {
                        // Store the parentPid
                        ppids[count] = (jlong) ppid;
                    }
                }
                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);
    }

    closedir(dir);
    // If more pids than array had size for; count will be greater than array size
    return count;
}

/*
 * Returns the parent pid of a given pid, or -1 if not found
 */
static pid_t parentPid(JNIEnv *env, pid_t pid) {
    FILE* fp;
    pstatus_t pstatus;
    int statlen;
    char fn[32];
    int i, p;
    char* s;

    /*
     * Try to open /proc/%d/status
     */
    snprintf(fn, sizeof fn, "/proc/%d/status", pid);
    fp = fopen(fn, "r");
    if (fp == NULL) {
        return -1;
    }

    /*
     * The format is: pid (command) state ppid ...
     * As the command could be anything we must find the right most
     * ")" and then skip the white spaces that follow it.
     */
    statlen = fread(&pstatus, 1, (sizeof pstatus), fp);
    fclose(fp);
    if (statlen < 0) {
        return -1;
    }
    return (pid_t) pstatus.pr_ppid;
}

/**************************************************************
 * Implementation of ProcessHandleImpl_Info native methods.
 */

/*
 * Fill in the Info object from the OS information about the process.
 *
 * Class:     java_lang_ProcessHandleImpl_Info
 * Method:    info0
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
  (JNIEnv *env, jobject jinfo, jlong jpid) {
    pid_t pid = (pid_t) jpid;
    getStatInfo(env, jinfo, pid);
    getCmdlineInfo(env, jinfo, pid);
}

/**
 * Read /proc/<pid>/stat and fill in the fields of the Info object.
 * Gather the user and system times.
 */
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
    FILE* fp;
    pstatus_t pstatus;
    struct stat stat_buf;
    int ret;
    char fn[32];
    int i, p;
    char* s;
    jlong totalTime;

    /*
     * Try to open /proc/%d/status
     */
    snprintf(fn, sizeof fn, "/proc/%d/status", pid);

    if (stat(fn, &stat_buf) < 0) {
        return;
    }

    fp = fopen(fn, "r");
    if (fp == NULL) {
        return;
    }

    ret = fread(&pstatus, 1, (sizeof pstatus), fp);
    fclose(fp);
    if (ret < 0) {
        return;
    }

    totalTime = pstatus.pr_utime.tv_sec * 1000000000L + pstatus.pr_utime.tv_nsec +
                pstatus.pr_stime.tv_sec * 1000000000L + pstatus.pr_stime.tv_nsec;

    (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
    JNU_CHECK_EXCEPTION(env);
}

static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
    FILE* fp;
    psinfo_t psinfo;
    int ret;
    char fn[32];
    char exePath[PATH_MAX];
    int i, p;
    jlong startTime;
    jobjectArray cmdArray;
    jstring str = NULL;

    /*
     * try to open /proc/%d/psinfo
     */
    snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);
    fp = fopen(fn, "r");
    if (fp == NULL) {
        return;
    }

    /*
     * The format is: pid (command) state ppid ...
     * As the command could be anything we must find the right most
     * ")" and then skip the white spaces that follow it.
     */
    ret = fread(&psinfo, 1, (sizeof psinfo), fp);
    fclose(fp);
    if (ret < 0) {
        return;
    }

    CHECK_NULL((str = uidToUser(env, psinfo.pr_uid)));
    (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, str);
    JNU_CHECK_EXCEPTION(env);

    startTime = (jlong)psinfo.pr_start.tv_sec * (jlong)1000 +
                (jlong)psinfo.pr_start.tv_nsec / 1000000;
    (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
    JNU_CHECK_EXCEPTION(env);

    /*
     * The path to the executable command is the link in /proc/<pid>/paths/a.out.
     */
    snprintf(fn, sizeof fn, "/proc/%d/path/a.out", pid);
    if ((ret = readlink(fn, exePath, PATH_MAX - 1)) < 0) {
        return;
    }

    // null terminate and create String to store for command
    exePath[ret] = '\0';
    CHECK_NULL(str = JNU_NewStringPlatform(env, exePath));
    (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
    JNU_CHECK_EXCEPTION(env);
}