Path: blob/master/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c
41119 views
/*1* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#include "jni.h"26#include "jni_util.h"27#include "java_lang_ProcessHandleImpl.h"28#include "java_lang_ProcessHandleImpl_Info.h"2930#include "ProcessHandleImpl_unix.h"313233#include <stdio.h>34#include <errno.h>35#include <fcntl.h>36#include <signal.h>37#include <stdlib.h>38#include <unistd.h>39#include <string.h>40#include <dirent.h>41#include <ctype.h>42#include <limits.h>43#include <sys/types.h>44#include <sys/stat.h>45#include <sys/wait.h>4647#include <pwd.h>4849#if defined(_AIX)50#include <sys/procfs.h>51#define DIR DIR6452#define dirent dirent6453#define opendir opendir6454#define readdir readdir6455#define closedir closedir6456#endif5758/**59* This file contains the implementation of the native ProcessHandleImpl60* functions which are common to all Unix variants.61*62* The currently supported Unix variants are Solaris, Linux, MaxOS X and AIX.63* The various similarities and differences between these systems make it hard64* to find a clear boundary between platform specific and shared code.65*66* In order to ease code sharing between the platforms while still keeping the67* code as clean as possible (i.e. free of preprocessor macros) we use the68* following source code layout (remember that ProcessHandleImpl_unix.c will69* be compiled on EVERY Unix platform while ProcessHandleImpl_<os>.c will be70* only compiled on the specific OS):71*72* - all the JNI wrappers for the ProcessHandleImpl functions go into this file73* - if their implementation is common on ALL the supported Unix platforms it74* goes right into the JNI wrappers75* - if the whole function or substantial parts of it are platform dependent,76* the implementation goes into os_<function_name> functions in77* ProcessHandleImpl_<os>.c78* - if at least two platforms implement an os_<function_name> function in the79* same way, this implementation is factored out into unix_<function_name>,80* placed into this file and called from the corresponding os_<function_name>81* function.82* - For convenience, all the os_ and unix_ functions are declared in83* ProcessHandleImpl_unix.h which is included into every84* ProcessHandleImpl_<os>.c file.85*86* Example 1:87* ----------88* The implementation of Java_java_lang_ProcessHandleImpl_initNative()89* is the same on all platforms except on Linux where it initilizes one90* additional field. So we place the implementation right into91* Java_java_lang_ProcessHandleImpl_initNative() but add call to92* os_init() at the end of the function which is empty on all platforms93* except Linux where it performs the additionally initializations.94*95* Example 2:96* ----------97* The implementation of Java_java_lang_ProcessHandleImpl_00024Info_info0 is the98* same on Solaris and AIX but different on Linux and MacOSX. We therefore simply99* call the helpers os_getParentPidAndTimings() and os_getCmdlineAndUserInfo().100* The Linux and MaxOS X versions of these functions (in the corresponding files101* ProcessHandleImpl_linux.c and ProcessHandleImpl_macosx.c) directly contain102* the platform specific implementations while the Solaris and AIX103* implementations simply call back to unix_getParentPidAndTimings() and104* unix_getCmdlineAndUserInfo() which are implemented right in this file.105*106* The term "same implementation" is still a question of interpretation. It my107* be acceptable to have a few ifdef'ed lines if that allows the sharing of a108* huge function. On the other hand, if the platform specific code in a shared109* function grows over a certain limit, it may be better to refactor that110* functionality into corresponding, platform-specific os_ functions.111*/112113114#ifndef WIFEXITED115#define WIFEXITED(status) (((status)&0xFF) == 0)116#endif117118#ifndef WEXITSTATUS119#define WEXITSTATUS(status) (((status)>>8)&0xFF)120#endif121122#ifndef WIFSIGNALED123#define WIFSIGNALED(status) (((status)&0xFF) > 0 && ((status)&0xFF00) == 0)124#endif125126#ifndef WTERMSIG127#define WTERMSIG(status) ((status)&0x7F)128#endif129130/* The child exited because of a signal.131* The best value to return is 0x80 + signal number,132* because that is what all Unix shells do, and because133* it allows callers to distinguish between process exit and134* process death by signal.135*/136#define WTERMSIG_RETURN(status) (WTERMSIG(status) + 0x80)137138#define RESTARTABLE(_cmd, _result) do { \139do { \140_result = _cmd; \141} while((_result == -1) && (errno == EINTR)); \142} while(0)143144#define RESTARTABLE_RETURN_PTR(_cmd, _result) do { \145do { \146_result = _cmd; \147} while((_result == NULL) && (errno == EINTR)); \148} while(0)149150151/* Field id for jString 'command' in java.lang.ProcessHandleImpl.Info */152jfieldID ProcessHandleImpl_Info_commandID;153154/* Field id for jString 'commandLine' in java.lang.ProcessHandleImpl.Info */155jfieldID ProcessHandleImpl_Info_commandLineID;156157/* Field id for jString[] 'arguments' in java.lang.ProcessHandleImpl.Info */158jfieldID ProcessHandleImpl_Info_argumentsID;159160/* Field id for jlong 'totalTime' in java.lang.ProcessHandleImpl.Info */161jfieldID ProcessHandleImpl_Info_totalTimeID;162163/* Field id for jlong 'startTime' in java.lang.ProcessHandleImpl.Info */164jfieldID ProcessHandleImpl_Info_startTimeID;165166/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */167jfieldID ProcessHandleImpl_Info_userID;168169/* Size of password or group entry when not available via sysconf */170#define ENT_BUF_SIZE 1024171/* The value for the size of the buffer used by getpwuid_r(). The result of */172/* sysconf(_SC_GETPW_R_SIZE_MAX) if available or ENT_BUF_SIZE otherwise. */173static long getpw_buf_size;174175/**************************************************************176* Static method to initialize field IDs and the ticks per second rate.177*178* Class: java_lang_ProcessHandleImpl_Info179* Method: initIDs180* Signature: ()V181*/182JNIEXPORT void JNICALL183Java_java_lang_ProcessHandleImpl_00024Info_initIDs(JNIEnv *env, jclass clazz) {184185CHECK_NULL(ProcessHandleImpl_Info_commandID =186(*env)->GetFieldID(env, clazz, "command", "Ljava/lang/String;"));187CHECK_NULL(ProcessHandleImpl_Info_commandLineID =188(*env)->GetFieldID(env, clazz, "commandLine", "Ljava/lang/String;"));189CHECK_NULL(ProcessHandleImpl_Info_argumentsID =190(*env)->GetFieldID(env, clazz, "arguments", "[Ljava/lang/String;"));191CHECK_NULL(ProcessHandleImpl_Info_totalTimeID =192(*env)->GetFieldID(env, clazz, "totalTime", "J"));193CHECK_NULL(ProcessHandleImpl_Info_startTimeID =194(*env)->GetFieldID(env, clazz, "startTime", "J"));195CHECK_NULL(ProcessHandleImpl_Info_userID =196(*env)->GetFieldID(env, clazz, "user", "Ljava/lang/String;"));197}198199/***********************************************************200* Static method to initialize platform dependent constants.201*202* Class: java_lang_ProcessHandleImpl203* Method: initNative204* Signature: ()V205*/206JNIEXPORT void JNICALL207Java_java_lang_ProcessHandleImpl_initNative(JNIEnv *env, jclass clazz) {208getpw_buf_size = sysconf(_SC_GETPW_R_SIZE_MAX);209if (getpw_buf_size == -1) {210getpw_buf_size = ENT_BUF_SIZE;211}212os_initNative(env, clazz);213}214215/* Block until a child process exits and return its exit code.216* Note, can only be called once for any given pid if reapStatus = true.217*218* Class: java_lang_ProcessHandleImpl219* Method: waitForProcessExit0220* Signature: (JZ)I221*/222JNIEXPORT jint JNICALL223Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,224jclass junk,225jlong jpid,226jboolean reapStatus) {227pid_t pid = (pid_t)jpid;228errno = 0;229230if (reapStatus != JNI_FALSE) {231/* Wait for the child process to exit.232* waitpid() is standard, so use it on all POSIX platforms.233* It is known to work when blocking to wait for the pid234* This returns immediately if the child has already exited.235*/236int status;237while (waitpid(pid, &status, 0) < 0) {238switch (errno) {239case ECHILD:240return java_lang_ProcessHandleImpl_NOT_A_CHILD; // No child241case EINTR: break;242default: return -1;243}244}245246if (WIFEXITED(status)) {247return WEXITSTATUS(status);248} else if (WIFSIGNALED(status)) {249return WTERMSIG_RETURN(status);250} else {251return status;252}253} else {254/*255* Wait for the child process to exit without reaping the exitValue.256* waitid() is standard on all POSIX platforms.257* Note: waitid on Mac OS X 10.7 seems to be broken;258* it does not return the exit status consistently.259*/260siginfo_t siginfo;261int options = WEXITED | WNOWAIT;262memset(&siginfo, 0, sizeof siginfo);263while (waitid(P_PID, pid, &siginfo, options) < 0) {264switch (errno) {265case ECHILD:266return java_lang_ProcessHandleImpl_NOT_A_CHILD; // No child267case EINTR: break;268default: return -1;269}270}271272if (siginfo.si_code == CLD_EXITED) {273/*274* The child exited normally; get its exit code.275*/276return siginfo.si_status;277} else if (siginfo.si_code == CLD_KILLED || siginfo.si_code == CLD_DUMPED) {278return WTERMSIG_RETURN(siginfo.si_status);279} else {280/*281* Unknown exit code; pass it through.282*/283return siginfo.si_status;284}285}286}287288/*289* Class: java_lang_ProcessHandleImpl290* Method: getCurrentPid0291* Signature: ()J292*/293JNIEXPORT jlong JNICALL294Java_java_lang_ProcessHandleImpl_getCurrentPid0(JNIEnv *env, jclass clazz) {295pid_t pid = getpid();296return (jlong) pid;297}298299/*300* Class: java_lang_ProcessHandleImpl301* Method: destroy0302* Signature: (JJZ)Z303*/304JNIEXPORT jboolean JNICALL305Java_java_lang_ProcessHandleImpl_destroy0(JNIEnv *env,306jobject obj,307jlong jpid,308jlong startTime,309jboolean force) {310pid_t pid = (pid_t) jpid;311int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;312jlong start = Java_java_lang_ProcessHandleImpl_isAlive0(env, obj, jpid);313314if (start == startTime || start == 0 || startTime == 0) {315return (kill(pid, sig) < 0) ? JNI_FALSE : JNI_TRUE;316} else {317return JNI_FALSE;318}319}320321/*322* Returns the children of the requested pid and optionally each parent and323* start time.324* Accumulates any process who parent pid matches.325* The resulting pids are stored into the array of longs.326* The number of pids is returned if they all fit.327* If the array is too short, the negative of the desired length is returned.328* Class: java_lang_ProcessHandleImpl329* Method: getProcessPids0330* Signature: (J[J[J[J)I331*/332JNIEXPORT jint JNICALL333Java_java_lang_ProcessHandleImpl_getProcessPids0(JNIEnv *env,334jclass clazz,335jlong jpid,336jlongArray jarray,337jlongArray jparentArray,338jlongArray jstimesArray) {339return os_getChildren(env, jpid, jarray, jparentArray, jstimesArray);340}341342/*343* Fill in the Info object from the OS information about the process.344*345* Class: java_lang_ProcessHandleImpl_Info346* Method: info0347* Signature: (Ljava/lang/ProcessHandle/Info;J)I348*/349JNIEXPORT void JNICALL350Java_java_lang_ProcessHandleImpl_00024Info_info0(JNIEnv *env,351jobject jinfo,352jlong jpid) {353pid_t pid = (pid_t) jpid;354pid_t ppid;355jlong totalTime = -1L;356jlong startTime = -1L;357358ppid = os_getParentPidAndTimings(env, pid, &totalTime, &startTime);359if (ppid >= 0) {360(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);361JNU_CHECK_EXCEPTION(env);362363(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);364JNU_CHECK_EXCEPTION(env);365}366os_getCmdlineAndUserInfo(env, jinfo, pid);367}368369/*370* Check if a process is alive.371* Return the start time (ms since 1970) if it is available.372* If the start time is not available return 0.373* If the pid is invalid, return -1.374*375* Class: java_lang_ProcessHandleImpl376* Method: isAlive0377* Signature: (J)J378*/379JNIEXPORT jlong JNICALL380Java_java_lang_ProcessHandleImpl_isAlive0(JNIEnv *env, jobject obj, jlong jpid) {381pid_t pid = (pid_t) jpid;382jlong startTime = 0L;383jlong totalTime = 0L;384pid_t ppid = os_getParentPidAndTimings(env, pid, &totalTime, &startTime);385return (ppid < 0) ? -1 : startTime;386}387388/*389* Returns the parent pid of the requested pid.390* The start time of the process must match (or be ANY).391*392* Class: java_lang_ProcessHandleImpl393* Method: parent0394* Signature: (JJ)J395*/396JNIEXPORT jlong JNICALL397Java_java_lang_ProcessHandleImpl_parent0(JNIEnv *env,398jobject obj,399jlong jpid,400jlong startTime) {401pid_t pid = (pid_t) jpid;402pid_t ppid;403404if (pid == getpid()) {405ppid = getppid();406} else {407jlong start = 0L;408jlong total = 0L; // unused409ppid = os_getParentPidAndTimings(env, pid, &total, &start);410if (start != startTime && start != 0 && startTime != 0) {411ppid = -1;412}413}414return (jlong) ppid;415}416417/**418* Construct the argument array by parsing the arguments from the sequence419* of arguments.420*/421void unix_fillArgArray(JNIEnv *env, jobject jinfo, int nargs, char *cp,422char *argsEnd, jstring cmdexe, char *cmdline) {423jobject argsArray;424int i;425426(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, cmdexe);427JNU_CHECK_EXCEPTION(env);428429if (nargs >= 1) {430// Create a String array for nargs-1 elements431jclass clazzString = JNU_ClassString(env);432CHECK_NULL(clazzString);433argsArray = (*env)->NewObjectArray(env, nargs - 1, clazzString, NULL);434CHECK_NULL(argsArray);435436for (i = 0; i < nargs - 1; i++) {437jstring str = NULL;438439cp += strlen(cp) + 1;440if (cp > argsEnd || *cp == '\0') {441return; // Off the end pointer or an empty argument is an error442}443444CHECK_NULL((str = JNU_NewStringPlatform(env, cp)));445446(*env)->SetObjectArrayElement(env, argsArray, i, str);447JNU_CHECK_EXCEPTION(env);448}449(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_argumentsID, argsArray);450JNU_CHECK_EXCEPTION(env);451}452if (cmdline != NULL) {453jstring commandLine = NULL;454CHECK_NULL((commandLine = JNU_NewStringPlatform(env, cmdline)));455(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandLineID, commandLine);456JNU_CHECK_EXCEPTION(env);457}458}459460void unix_getUserInfo(JNIEnv* env, jobject jinfo, uid_t uid) {461int result = 0;462char* pwbuf;463jstring name = NULL;464465/* allocate buffer for password record */466pwbuf = (char*)malloc(getpw_buf_size);467if (pwbuf == NULL) {468JNU_ThrowOutOfMemoryError(env, "Unable to open getpwent");469} else {470struct passwd pwent;471struct passwd* p = NULL;472RESTARTABLE(getpwuid_r(uid, &pwent, pwbuf, (size_t)getpw_buf_size, &p), result);473474// Create the Java String if a name was found475if (result == 0 && p != NULL &&476p->pw_name != NULL && *(p->pw_name) != '\0') {477name = JNU_NewStringPlatform(env, p->pw_name);478}479free(pwbuf);480}481if (name != NULL) {482(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);483}484}485486/*487* The following functions are common on Solaris, Linux and AIX.488*/489490#if defined (__linux__) || defined(_AIX)491492/*493* Returns the children of the requested pid and optionally each parent and494* start time.495* Reads /proc and accumulates any process who parent pid matches.496* The resulting pids are stored into the array of longs.497* The number of pids is returned if they all fit.498* If the array is too short, the negative of the desired length is returned.499*/500jint unix_getChildren(JNIEnv *env, jlong jpid, jlongArray jarray,501jlongArray jparentArray, jlongArray jstimesArray) {502DIR* dir;503struct dirent* ptr;504pid_t pid = (pid_t) jpid;505jlong* pids = NULL;506jlong* ppids = NULL;507jlong* stimes = NULL;508jsize parentArraySize = 0;509jsize arraySize = 0;510jsize stimesSize = 0;511jsize count = 0;512513arraySize = (*env)->GetArrayLength(env, jarray);514JNU_CHECK_EXCEPTION_RETURN(env, -1);515if (jparentArray != NULL) {516parentArraySize = (*env)->GetArrayLength(env, jparentArray);517JNU_CHECK_EXCEPTION_RETURN(env, -1);518519if (arraySize != parentArraySize) {520JNU_ThrowIllegalArgumentException(env, "array sizes not equal");521return 0;522}523}524if (jstimesArray != NULL) {525stimesSize = (*env)->GetArrayLength(env, jstimesArray);526JNU_CHECK_EXCEPTION_RETURN(env, -1);527528if (arraySize != stimesSize) {529JNU_ThrowIllegalArgumentException(env, "array sizes not equal");530return 0;531}532}533534/*535* To locate the children we scan /proc looking for files that have a536* position integer as a filename.537*/538if ((dir = opendir("/proc")) == NULL) {539JNU_ThrowByNameWithLastError(env,540"java/lang/RuntimeException", "Unable to open /proc");541return -1;542}543544do { // Block to break out of on Exception545pids = (*env)->GetLongArrayElements(env, jarray, NULL);546if (pids == NULL) {547break;548}549if (jparentArray != NULL) {550ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);551if (ppids == NULL) {552break;553}554}555if (jstimesArray != NULL) {556stimes = (*env)->GetLongArrayElements(env, jstimesArray, NULL);557if (stimes == NULL) {558break;559}560}561562while ((ptr = readdir(dir)) != NULL) {563pid_t ppid = 0;564jlong totalTime = 0L;565jlong startTime = 0L;566567/* skip files that aren't numbers */568pid_t childpid = (pid_t) atoi(ptr->d_name);569if ((int) childpid <= 0) {570continue;571}572573// Get the parent pid, and start time574ppid = os_getParentPidAndTimings(env, childpid, &totalTime, &startTime);575if (ppid >= 0 && (pid == 0 || ppid == pid)) {576if (count < arraySize) {577// Only store if it fits578pids[count] = (jlong) childpid;579580if (ppids != NULL) {581// Store the parentPid582ppids[count] = (jlong) ppid;583}584if (stimes != NULL) {585// Store the process start time586stimes[count] = startTime;587}588}589count++; // Count to tabulate size needed590}591}592} while (0);593594if (pids != NULL) {595(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);596}597if (ppids != NULL) {598(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);599}600if (stimes != NULL) {601(*env)->ReleaseLongArrayElements(env, jstimesArray, stimes, 0);602}603604closedir(dir);605// If more pids than array had size for; count will be greater than array size606return count;607}608609#endif // defined (__linux__) || defined(_AIX)610611/*612* The following functions are for AIX.613*/614615#if defined(_AIX)616617/**618* Helper function to get the 'psinfo_t' data from "/proc/%d/psinfo".619* Returns 0 on success and -1 on error.620*/621static int getPsinfo(pid_t pid, psinfo_t *psinfo) {622FILE* fp;623char fn[32];624int ret;625626/*627* Try to open /proc/%d/psinfo628*/629snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);630fp = fopen(fn, "r");631if (fp == NULL) {632return -1;633}634635ret = fread(psinfo, 1, sizeof(psinfo_t), fp);636fclose(fp);637if (ret < sizeof(psinfo_t)) {638return -1;639}640return 0;641}642643/**644* Read /proc/<pid>/psinfo and return the ppid, total cputime and start time.645* Return: -1 is fail; >= 0 is parent pid646* 'total' will contain the running time of 'pid' in nanoseconds.647* 'start' will contain the start time of 'pid' in milliseconds since epoch.648*/649pid_t unix_getParentPidAndTimings(JNIEnv *env, pid_t pid,650jlong *totalTime, jlong* startTime) {651psinfo_t psinfo;652653if (getPsinfo(pid, &psinfo) < 0) {654return -1;655}656657// Validate the pid before returning the info658if (kill(pid, 0) < 0) {659return -1;660}661662*totalTime = psinfo.pr_time.tv_sec * 1000000000L + psinfo.pr_time.tv_nsec;663664*startTime = psinfo.pr_start.tv_sec * (jlong)1000 +665psinfo.pr_start.tv_nsec / 1000000;666667return (pid_t) psinfo.pr_ppid;668}669670void unix_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) {671psinfo_t psinfo;672char fn[32];673char exePath[PATH_MAX];674char prargs[PRARGSZ + 1];675jstring cmdexe = NULL;676int ret;677678/*679* Now try to open /proc/%d/psinfo680*/681if (getPsinfo(pid, &psinfo) < 0) {682unix_fillArgArray(env, jinfo, 0, NULL, NULL, cmdexe, NULL);683return;684}685686unix_getUserInfo(env, jinfo, psinfo.pr_uid);687688/*689* Now read psinfo.pr_psargs which contains the first PRARGSZ characters of the690* argument list (i.e. arg[0] arg[1] ...). Unfortunately, PRARGSZ is usually set691* to 80 characters only. Nevertheless it's better than nothing :)692*/693strncpy(prargs, psinfo.pr_psargs, PRARGSZ);694prargs[PRARGSZ] = '\0';695if (prargs[0] == '\0') {696/* If psinfo.pr_psargs didn't contain any strings, use psinfo.pr_fname697* (which only contains the last component of exec()ed pathname) as a698* last resort. This is true for AIX kernel processes for example.699*/700strncpy(prargs, psinfo.pr_fname, PRARGSZ);701prargs[PRARGSZ] = '\0';702}703unix_fillArgArray(env, jinfo, 0, NULL, NULL, cmdexe,704prargs[0] == '\0' ? NULL : prargs);705}706707#endif // defined(_AIX)708709710