1 /*
   2  * Copyright (c) 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 static long clock_ticks_per_second;
  55 static int pageSize;
  56 
  57 void os_initNative(JNIEnv *env, jclass clazz) {
  58     bootTime_ms = getBoottime(env);
  59     clock_ticks_per_second = sysconf(_SC_CLK_TCK);
  60     pageSize = sysconf(_SC_PAGESIZE);
  61 }
  62 
  63 jint os_getChildren(JNIEnv *env, jlong jpid, jlongArray jarray,
  64                     jlongArray jparentArray, jlongArray jstimesArray) {
  65     return unix_getChildren(env, jpid, jarray, jparentArray, jstimesArray);
  66 }
  67 
  68 /**
  69  * Read /proc/<pid>/stat and return the ppid, total cputime and start time.
  70  * -1 is fail;  >=  0 is parent pid
  71  * 'total' will contain the running time of 'pid' in nanoseconds.
  72  * 'start' will contain the start time of 'pid' in milliseconds since epoch.
  73  */
  74 pid_t os_getParentPidAndTimings(JNIEnv *env, pid_t pid,
  75                                 jlong *totalTime, jlong* startTime) {
  76     FILE* fp;
  77     char buffer[2048];
  78     int statlen;
  79     char fn[32];
  80     char* s;
  81     int parentPid;
  82     long unsigned int utime = 0;      // clock tics
  83     long unsigned int stime = 0;      // clock tics
  84     long long unsigned int start = 0; // microseconds
  85 
  86     /*
  87      * Try to stat and then open /proc/%d/stat
  88      */
  89     snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
  90 
  91     fp = fopen(fn, "r");
  92     if (fp == NULL) {
  93         return -1;              // fail, no such /proc/pid/stat
  94     }
  95 
  96     /*
  97      * The format is: pid (command) state ppid ...
  98      * As the command could be anything we must find the right most
  99      * ")" and then skip the white spaces that follow it.
 100      */
 101     statlen = fread(buffer, 1, (sizeof buffer - 1), fp);
 102     fclose(fp);
 103     if (statlen < 0) {
 104         return -1;               // parent pid is not available
 105     }
 106 
 107     buffer[statlen] = '\0';
 108     s = strchr(buffer, '(');
 109     if (s == NULL) {
 110         return -1;               // parent pid is not available
 111     }
 112     // Found start of command, skip to end
 113     s++;
 114     s = strrchr(s, ')');
 115     if (s == NULL) {
 116         return -1;               // parent pid is not available
 117     }
 118     s++;
 119 
 120     // Scan the needed fields from status, retaining only ppid(4),
 121     // utime (14), stime(15), starttime(22)
 122     if (4 != sscanf(s, " %*c %d %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %llu",
 123             &parentPid, &utime, &stime, &start)) {
 124         return 0;              // not all values parsed; return error
 125     }
 126 
 127     *totalTime = (utime + stime) * (jlong)(1000000000 / clock_ticks_per_second);
 128 
 129     *startTime = bootTime_ms + ((start * 1000) / clock_ticks_per_second);
 130 
 131     return parentPid;
 132 }
 133 
 134 void os_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
 135     int fd;
 136     int cmdlen = 0;
 137     char *cmdline = NULL, *cmdEnd = NULL; // used for command line args and exe
 138     char *args = NULL;
 139     jstring cmdexe = NULL;
 140     char fn[32];
 141     struct stat stat_buf;
 142 
 143     /*
 144      * Try to open /proc/<pid>/cmdline
 145      */
 146     snprintf(fn, sizeof fn, "/proc/%d/cmdline", pid);
 147     if ((fd = open(fn, O_RDONLY)) < 0) {
 148         return;
 149     }
 150 
 151     if (fstat(fd, &stat_buf) == 0) {
 152         unix_getUserInfo(env, jinfo, stat_buf.st_uid);
 153     }
 154 
 155     do {                // Block to break out of on errors
 156         int i, truncated = 0;
 157         int count;
 158         char *s;
 159 
 160         /*
 161          * The path name read by readlink() is limited to PATH_MAX characters.
 162          * The content of /proc/<pid>/cmdline is limited to PAGE_SIZE characters.
 163          */
 164         cmdline = (char*)malloc((PATH_MAX > pageSize ? PATH_MAX : pageSize) + 1);
 165         if (cmdline == NULL) {
 166             break;
 167         }
 168 
 169         /*
 170          * On Linux, the full path to the executable command is the link in
 171          * /proc/<pid>/exe. But it is only readable for processes we own.
 172          */
 173         snprintf(fn, sizeof fn, "/proc/%d/exe", pid);
 174         if ((cmdlen = readlink(fn, cmdline, PATH_MAX)) > 0) {
 175             // null terminate and create String to store for command
 176             cmdline[cmdlen] = '\0';
 177             cmdexe = JNU_NewStringPlatform(env, cmdline);
 178             (*env)->ExceptionClear(env);        // unconditionally clear any exception
 179         }
 180 
 181         /*
 182          * The command-line arguments appear as a set of strings separated by
 183          * null bytes ('\0'), with a further null byte after the last
 184          * string. The last string is only null terminated if the whole command
 185          * line is not exceeding (PAGE_SIZE - 1) characters.
 186          */
 187         cmdlen = 0;
 188         s = cmdline;
 189         while ((count = read(fd, s, pageSize - cmdlen)) > 0) {
 190             cmdlen += count;
 191             s += count;
 192         }
 193         if (count < 0) {
 194             break;
 195         }
 196         // We have to null-terminate because the process may have changed argv[]
 197         // or because the content in /proc/<pid>/cmdline is truncated.
 198         cmdline[cmdlen] = '\0';
 199         if (cmdlen == pageSize && cmdline[pageSize - 1] != '\0') {
 200             truncated = 1;
 201         } else if (cmdlen == 0) {
 202             // /proc/<pid>/cmdline was empty. This usually happens for kernel processes
 203             // like '[kthreadd]'. We could try to read /proc/<pid>/comm in the future.
 204         }
 205         if (cmdlen > 0 && (cmdexe == NULL || truncated)) {
 206             // We have no exact command or the arguments are truncated.
 207             // In this case we save the command line from /proc/<pid>/cmdline.
 208             args = (char*)malloc(pageSize + 1);
 209             if (args != NULL) {
 210                 memcpy(args, cmdline, cmdlen + 1);
 211                 for (i = 0; i < cmdlen; i++) {
 212                     if (args[i] == '\0') {
 213                         args[i] = ' ';
 214                     }
 215                 }
 216             }
 217         }
 218         i = 0;
 219         if (!truncated) {
 220             // Count the arguments
 221             cmdEnd = &cmdline[cmdlen];
 222             for (s = cmdline; *s != '\0' && (s < cmdEnd); i++) {
 223                 s += strnlen(s, (cmdEnd - s)) + 1;
 224             }
 225         }
 226         unix_fillArgArray(env, jinfo, i, cmdline, cmdEnd, cmdexe, args);
 227     } while (0);
 228 
 229     if (cmdline != NULL) {
 230         free(cmdline);
 231     }
 232     if (args != NULL) {
 233         free(args);
 234     }
 235     if (fd >= 0) {
 236         close(fd);
 237     }
 238 }
 239 
 240 /**
 241  * Read the boottime from /proc/stat.
 242  */
 243 static long long getBoottime(JNIEnv *env) {
 244     FILE *fp;
 245     char *line = NULL;
 246     size_t len = 0;
 247     long long bootTime = 0;
 248 
 249     fp = fopen("/proc/stat", "r");
 250     if (fp == NULL) {
 251         return -1;
 252     }
 253 
 254     while (getline(&line, &len, fp) != -1) {
 255         if (sscanf(line, "btime %llu", &bootTime) == 1) {
 256             break;
 257         }
 258     }
 259     free(line);
 260 
 261     if (fp != 0) {
 262         fclose(fp);
 263     }
 264 
 265     return bootTime * 1000;
 266 }