1 /*
   2  * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include "jni.h"
  27 #include "jni_util.h"
  28 #include "java_lang_ProcessHandleImpl.h"
  29 #include "java_lang_ProcessHandleImpl_Info.h"
  30 
  31 
  32 #include <stdio.h>
  33 #include <ctype.h>
  34 #include <dirent.h>
  35 #include <errno.h>
  36 #include <fcntl.h>
  37 #include <procfs.h>
  38 #include <signal.h>
  39 #include <stdlib.h>
  40 #include <sys/stat.h>
  41 #include <unistd.h>
  42 #include <limits.h>
  43 
  44 /**
  45  * Implementations of ProcessHandleImpl functions that are
  46  * NOT common to all Unix variants:
  47  * - getProcessPids0(pid, pidArray)
  48  *
  49  * Implementations of ProcessHandleImpl_Info
  50  * - totalTime, startTime
  51  * - Command
  52  * - Arguments
  53  */
  54 
  55 /*
  56  * Signatures for internal OS specific functions.
  57  */
  58 static pid_t parentPid(JNIEnv *env, pid_t pid);
  59 static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
  60 static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);
  61 
  62 extern jstring uidToUser(JNIEnv* env, uid_t uid);
  63 
  64 /* Field id for jString 'command' in java.lang.ProcessHandle.Info */
  65 static jfieldID ProcessHandleImpl_Info_commandID;
  66 
  67 /* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
  68 static jfieldID ProcessHandleImpl_Info_argumentsID;
  69 
  70 /* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
  71 static jfieldID ProcessHandleImpl_Info_totalTimeID;
  72 
  73 /* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
  74 static jfieldID ProcessHandleImpl_Info_startTimeID;
  75 
  76 /* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
  77 static jfieldID ProcessHandleImpl_Info_userID;
  78 
  79 /* static value for clock ticks per second. */
  80 static long clock_ticks_per_second;
  81 
  82 /**************************************************************
  83  * Static method to initialize field IDs and the ticks per second rate.
  84  *
  85  * Class:     java_lang_ProcessHandleImpl_Info
  86  * Method:    initIDs
  87  * Signature: ()V
  88  */
  89 JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
  90   (JNIEnv *env, jclass clazz) {
  91 
  92     CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
  93         clazz, "command", "Ljava/lang/String;"));
  94     CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
  95         clazz, "arguments", "[Ljava/lang/String;"));
  96     CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
  97         clazz, "totalTime", "J"));
  98     CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
  99         clazz, "startTime", "J"));
 100     CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
 101         clazz, "user", "Ljava/lang/String;"));
 102     clock_ticks_per_second = sysconf(_SC_CLK_TCK);
 103 }
 104 
 105 /*
 106  * Returns the parent pid of the requested pid.
 107  *
 108  * Class:     java_lang_ProcessHandleImpl
 109  * Method:    parent0
 110  * Signature: (J)J
 111  */
 112 JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
 113 (JNIEnv *env, jobject obj, jlong jpid) {
 114     pid_t pid = (pid_t) jpid;
 115     pid_t ppid = -1;
 116 
 117     if (pid == getpid()) {
 118         ppid = getppid();
 119     } else {
 120         ppid = parentPid(env, pid);
 121     }
 122     return (jlong) ppid;
 123 }
 124 
 125 /*
 126  * Returns the children of the requested pid and optionally each parent.
 127  *
 128  * Class:     java_lang_ProcessHandleImpl
 129  * Method:    getChildPids
 130  * Signature: (J[J)I
 131  *
 132  * Reads /proc and accumulates any process who parent pid matches.
 133  * The resulting pids are stored into the array of longs.
 134  * The number of pids is returned if they all fit.
 135  * If the array is too short, the desired length is returned.
 136  */
 137 JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
 138 (JNIEnv *env, jclass clazz, jlong jpid,
 139     jlongArray jarray, jlongArray jparentArray)
 140 {
 141     DIR* dir;
 142     struct dirent* ptr;
 143     pid_t pid = (pid_t) jpid;
 144     size_t count = 0;
 145     jlong* pids = NULL;
 146     jlong* ppids = NULL;
 147     size_t parentArraySize = 0;
 148     size_t arraySize = 0;
 149 
 150     arraySize = (*env)->GetArrayLength(env, jarray);
 151     JNU_CHECK_EXCEPTION_RETURN(env, 0);
 152     if (jparentArray != NULL) {
 153         parentArraySize = (*env)->GetArrayLength(env, jparentArray);
 154         JNU_CHECK_EXCEPTION_RETURN(env, 0);
 155 
 156         if (arraySize != parentArraySize) {
 157             JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
 158             return 0;
 159         }
 160     }
 161 
 162     /*
 163      * To locate the children we scan /proc looking for files that have a
 164      * positive integer as a filename.
 165      */
 166     if ((dir = opendir("/proc")) == NULL) {
 167         JNU_ThrowByNameWithLastError(env,
 168             "java/lang/Runtime", "Unable to open /proc");
 169         return 0;
 170     }
 171 
 172     do { // Block to break out of on Exception
 173         pids = (*env)->GetLongArrayElements(env, jarray, NULL);
 174         if (pids == NULL) {
 175             break;
 176         }
 177         if (jparentArray != NULL) {
 178             ppids  = (*env)->GetLongArrayElements(env, jparentArray, NULL);
 179             if (ppids == NULL) {
 180                 break;
 181             }
 182         }
 183 
 184         while ((ptr = readdir(dir)) != NULL) {
 185             pid_t ppid;
 186 
 187             /* skip files that aren't numbers */
 188             pid_t childpid = (pid_t) atoi(ptr->d_name);
 189             if ((int) childpid <= 0) {
 190                 continue;
 191             }
 192 
 193             ppid = 0;
 194             if (pid != 0 || jparentArray != NULL) {
 195                 // parentPid opens and reads /proc/pid/stat
 196                 ppid = parentPid(env, childpid);
 197             }
 198             if (pid == 0 || ppid == pid) {
 199                 if (count < arraySize) {
 200                     // Only store if it fits
 201                     pids[count] = (jlong) childpid;
 202 
 203                     if (ppids != NULL) {
 204                         // Store the parentPid
 205                         ppids[count] = (jlong) ppid;
 206                     }
 207                 }
 208                 count++; // Count to tabulate size needed
 209             }
 210         }
 211     } while (0);
 212 
 213     if (pids != NULL) {
 214         (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
 215     }
 216     if (ppids != NULL) {
 217         (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
 218     }
 219 
 220     closedir(dir);
 221     // If more pids than array had size for; count will be greater than array size
 222     return count;
 223 }
 224 
 225 /*
 226  * Returns the parent pid of a given pid, or -1 if not found
 227  */
 228 static pid_t parentPid(JNIEnv *env, pid_t pid) {
 229     FILE* fp;
 230     pstatus_t pstatus;
 231     int statlen;
 232     char fn[32];
 233     int i, p;
 234     char* s;
 235 
 236     /*
 237      * Try to open /proc/%d/status
 238      */
 239     snprintf(fn, sizeof fn, "/proc/%d/status", pid);
 240     fp = fopen(fn, "r");
 241     if (fp == NULL) {
 242         return -1;
 243     }
 244 
 245     /*
 246      * The format is: pid (command) state ppid ...
 247      * As the command could be anything we must find the right most
 248      * ")" and then skip the white spaces that follow it.
 249      */
 250     statlen = fread(&pstatus, 1, (sizeof pstatus), fp);
 251     fclose(fp);
 252     if (statlen < 0) {
 253         return -1;
 254     }
 255     return (pid_t) pstatus.pr_ppid;
 256 }
 257 
 258 /**************************************************************
 259  * Implementation of ProcessHandleImpl_Info native methods.
 260  */
 261 
 262 /*
 263  * Fill in the Info object from the OS information about the process.
 264  *
 265  * Class:     java_lang_ProcessHandleImpl_Info
 266  * Method:    info0
 267  * Signature: (J)V
 268  */
 269 JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
 270   (JNIEnv *env, jobject jinfo, jlong jpid) {
 271     pid_t pid = (pid_t) jpid;
 272     getStatInfo(env, jinfo, pid);
 273     getCmdlineInfo(env, jinfo, pid);
 274 }
 275 
 276 /**
 277  * Read /proc/<pid>/stat and fill in the fields of the Info object.
 278  * Gather the user and system times.
 279  */
 280 static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
 281     FILE* fp;
 282     pstatus_t pstatus;
 283     struct stat stat_buf;
 284     int ret;
 285     char fn[32];
 286     int i, p;
 287     char* s;
 288     jlong totalTime;
 289 
 290     /*
 291      * Try to open /proc/%d/status
 292      */
 293     snprintf(fn, sizeof fn, "/proc/%d/status", pid);
 294 
 295     if (stat(fn, &stat_buf) < 0) {
 296         return;
 297     }
 298 
 299     fp = fopen(fn, "r");
 300     if (fp == NULL) {
 301         return;
 302     }
 303 
 304     ret = fread(&pstatus, 1, (sizeof pstatus), fp);
 305     fclose(fp);
 306     if (ret < 0) {
 307         return;
 308     }
 309 
 310     totalTime = pstatus.pr_utime.tv_sec * 1000000000L + pstatus.pr_utime.tv_nsec +
 311                 pstatus.pr_stime.tv_sec * 1000000000L + pstatus.pr_stime.tv_nsec;
 312 
 313     (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
 314     JNU_CHECK_EXCEPTION(env);
 315 }
 316 
 317 static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
 318     FILE* fp;
 319     psinfo_t psinfo;
 320     int ret;
 321     char fn[32];
 322     char exePath[PATH_MAX];
 323     int i, p;
 324     jlong startTime;
 325     jobjectArray cmdArray;
 326     jstring str = NULL;
 327 
 328     /*
 329      * try to open /proc/%d/psinfo
 330      */
 331     snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);
 332     fp = fopen(fn, "r");
 333     if (fp == NULL) {
 334         return;
 335     }
 336 
 337     /*
 338      * The format is: pid (command) state ppid ...
 339      * As the command could be anything we must find the right most
 340      * ")" and then skip the white spaces that follow it.
 341      */
 342     ret = fread(&psinfo, 1, (sizeof psinfo), fp);
 343     fclose(fp);
 344     if (ret < 0) {
 345         return;
 346     }
 347 
 348     CHECK_NULL((str = uidToUser(env, psinfo.pr_uid)));
 349     (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, str);
 350     JNU_CHECK_EXCEPTION(env);
 351 
 352     startTime = (jlong)psinfo.pr_start.tv_sec * (jlong)1000 +
 353                 (jlong)psinfo.pr_start.tv_nsec / 1000000;
 354     (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
 355     JNU_CHECK_EXCEPTION(env);
 356 
 357     /*
 358      * The path to the executable command is the link in /proc/<pid>/paths/a.out.
 359      */
 360     snprintf(fn, sizeof fn, "/proc/%d/path/a.out", pid);
 361     if ((ret = readlink(fn, exePath, PATH_MAX - 1)) < 0) {
 362         return;
 363     }
 364 
 365     // null terminate and create String to store for command
 366     exePath[ret] = '\0';
 367     CHECK_NULL(str = JNU_NewStringPlatform(env, exePath));
 368     (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
 369     JNU_CHECK_EXCEPTION(env);
 370 }
 371