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 #include "ProcessHandleImpl_unix.h"
  32 
  33 
  34 #include <fcntl.h>
  35 #include <limits.h>
  36 #include <stdlib.h>
  37 #include <unistd.h>
  38 #include <sys/types.h>
  39 #include <sys/stat.h>
  40 
  41 #include <string.h>
  42 #include <ctype.h>
  43 
  44 /*
  45  * Implementation of native ProcessHandleImpl functions for Linux.
  46  * See ProcessHandleImpl_unix.c for more details.
  47  */
  48 
  49 /* Signatures for internal OS specific functions. */
  50 static long long getBoottime(JNIEnv *env);
  51 
  52 /* A static offset in milliseconds since boot. */
  53 static long long bootTime_ms;
  54 
  55 void os_init(JNIEnv *env, jclass clazz) {
  56     bootTime_ms = getBoottime(env);
  57 }
  58 
  59 jint os_getChildren(JNIEnv *env, jlong jpid,
  60     jlongArray jarray, jlongArray jparentArray) {
  61     return unix_getChildren(env, jpid, jarray, jparentArray);
  62 }
  63 
  64 /*
  65  * Returns the parent pid of a given pid, or -1 if not found
  66  */
  67 pid_t os_parentPid(JNIEnv *env, pid_t pid) {
  68     char state;
  69     FILE* fp;
  70     char stat[2048];
  71     int statlen;
  72     char fn[32];
  73     int i, p;
  74     char* s;
  75 
  76     /*
  77      * try to open /proc/%d/stat
  78      */
  79     snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
  80     fp = fopen(fn, "r");
  81     if (fp == NULL) {
  82         return -1;
  83     }
  84 
  85     /*
  86      * The format is: pid (command) state ppid ...
  87      * As the command could be anything we must find the right most
  88      * ")" and then skip the white spaces that follow it.
  89      */
  90     statlen = fread(stat, 1, (sizeof stat - 1), fp);
  91     fclose(fp);
  92     if (statlen < 0) {
  93         return -1;
  94     }
  95 
  96     stat[statlen] = '\0';
  97     s = strrchr(stat, ')');
  98     if (s == NULL) {
  99         return -1;
 100     }
 101     do s++; while (isspace(*s));
 102     i = sscanf(s, "%c %d", &state, &p);
 103     if (i != 2) {
 104         return (pid_t)-1;
 105     }
 106     return (pid_t) p;
 107 }
 108 
 109 /**
 110  * Read /proc/<pid>/stat and fill in the fields of the Info object.
 111  * The executable name, plus the user, system, and start times are gathered.
 112  */
 113 void os_getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
 114     char state;
 115     FILE* fp;
 116     char buffer[2048];
 117     struct stat stat_buf;
 118     int statlen;
 119     char fn[32];
 120     int i, ppid = -2;
 121     char* s;
 122     char *cmd;
 123     jstring name = NULL;
 124     unsigned long userTime = 0;             // clock tics
 125     unsigned long totalTime = 0;            // clock tics
 126     jlong total = 0;                        // nano seconds
 127     unsigned long long startTime = 0;       // microseconds
 128 
 129     /*
 130      * Try to stat and then open /proc/%d/stat
 131      */
 132     snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
 133 
 134     if (stat(fn, &stat_buf) < 0) {
 135         return;
 136     }
 137 
 138     CHECK_NULL((name = unix_uidToUser(env, stat_buf.st_uid)));
 139     (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);
 140     JNU_CHECK_EXCEPTION(env);
 141 
 142     fp = fopen(fn, "r");
 143     if (fp == NULL) {
 144         return;
 145     }
 146 
 147     /*
 148      * The format is: pid (command) state ppid ...
 149      * As the command could be anything we must find the right most
 150      * ")" and then skip the white spaces that follow it.
 151      */
 152     statlen = fread(buffer, 1, (sizeof buffer - 1), fp);
 153     fclose(fp);
 154     if (statlen < 0) {
 155         return;
 156     }
 157 
 158     buffer[statlen] = '\0';
 159     s = strchr(buffer, '(');
 160     if (s == NULL) {
 161         return;
 162     }
 163     // Found start of command, skip to end
 164     s++;
 165     s = strrchr(s, ')');
 166     if (s == NULL) {
 167         return;
 168     }
 169     s++;
 170 
 171     // Scan the needed fields from status, retaining only ppid(4),
 172     // utime (14), stime(15), starttime(22)
 173     i = sscanf(s, " %c %d %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %llu",
 174             &state, &ppid, &userTime, &totalTime, &startTime);
 175     if (i != 5) {
 176         return;              // not all values parsed; return error
 177     }
 178 
 179     total = (userTime + totalTime) * (jlong)(1000000000 / clock_ticks_per_second);
 180 
 181     startTime = bootTime_ms + ((startTime * 1000) / clock_ticks_per_second);
 182 
 183     (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, total);
 184     JNU_CHECK_EXCEPTION(env);
 185     (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
 186     JNU_CHECK_EXCEPTION(env);
 187 }
 188 
 189 void os_getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
 190     int fd;
 191     int cmdlen = 0;
 192     char *cmdline = NULL, *cmdEnd;  // used for command line args and exe
 193     jstring cmdexe = NULL;
 194     char fn[32];
 195 
 196     /*
 197      * Try to open /proc/%d/cmdline
 198      */
 199     snprintf(fn, sizeof fn, "/proc/%d/cmdline", pid);
 200     if ((fd = open(fn, O_RDONLY)) < 0) {
 201         return;
 202     }
 203 
 204     do {                // Block to break out of on errors
 205         int i;
 206         char *s;
 207 
 208         cmdline = (char*)malloc(PATH_MAX);
 209         if (cmdline == NULL) {
 210             break;
 211         }
 212 
 213         /*
 214          * The path to the executable command is the link in /proc/<pid>/exe.
 215          */
 216         snprintf(fn, sizeof fn, "/proc/%d/exe", pid);
 217         if ((cmdlen = readlink(fn, cmdline, PATH_MAX - 1)) > 0) {
 218             // null terminate and create String to store for command
 219             cmdline[cmdlen] = '\0';
 220             cmdexe = JNU_NewStringPlatform(env, cmdline);
 221             (*env)->ExceptionClear(env);        // unconditionally clear any exception
 222         }
 223 
 224         /*
 225          * The buffer format is the arguments nul terminated with an extra nul.
 226          */
 227         cmdlen = read(fd, cmdline, PATH_MAX-1);
 228         if (cmdlen < 0) {
 229             break;
 230         }
 231 
 232         // Terminate the buffer and count the arguments
 233         cmdline[cmdlen] = '\0';
 234         cmdEnd = &cmdline[cmdlen + 1];
 235         for (s = cmdline,i = 0; *s != '\0' && (s < cmdEnd); i++) {
 236             s += strnlen(s, (cmdEnd - s)) + 1;
 237         }
 238 
 239         if (unix_fillArgArray(env, jinfo, i, cmdline, cmdEnd, cmdexe) < 0) {
 240             break;
 241         }
 242     } while (0);
 243 
 244     if (cmdline != NULL) {
 245         free(cmdline);
 246     }
 247     if (fd >= 0) {
 248         close(fd);
 249     }
 250 }
 251 
 252 /**
 253  * Read the boottime from /proc/stat.
 254  */
 255 static long long getBoottime(JNIEnv *env) {
 256     FILE *fp;
 257     char *line = NULL;
 258     size_t len = 0;
 259     long long bootTime = 0;
 260 
 261     fp = fopen("/proc/stat", "r");
 262     if (fp == NULL) {
 263         return -1;
 264     }
 265 
 266     while (getline(&line, &len, fp) != -1) {
 267         if (sscanf(line, "btime %llu", &bootTime) == 1) {
 268             break;
 269         }
 270     }
 271     free(line);
 272 
 273     if (fp != 0) {
 274         fclose(fp);
 275     }
 276 
 277     return bootTime * 1000;
 278 }