| 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 stat64 stat_buf; |
| 142 | |
| 143 | /* |
| 144 | * Stat /proc/<pid> to get the user id |
| 145 | */ |
| 146 | snprintf(fn, sizeof fn, "/proc/%d" , pid); |
| 147 | if (stat64(fn, &stat_buf) == 0) { |
| 148 | unix_getUserInfo(env, jinfo, stat_buf.st_uid); |
| 149 | JNU_CHECK_EXCEPTION(env); |
| 150 | } |
| 151 | |
| 152 | /* |
| 153 | * Try to open /proc/<pid>/cmdline |
| 154 | */ |
| 155 | strncat(fn, "/cmdline" , sizeof fn - strnlen(fn, sizeof fn) - 1); |
| 156 | if ((fd = open(fn, O_RDONLY)) < 0) { |
| 157 | return; |
| 158 | } |
| 159 | |
| 160 | do { // Block to break out of on errors |
| 161 | int i, truncated = 0; |
| 162 | int count; |
| 163 | char *s; |
| 164 | |
| 165 | /* |
| 166 | * The path name read by readlink() is limited to PATH_MAX characters. |
| 167 | * The content of /proc/<pid>/cmdline is limited to PAGE_SIZE characters. |
| 168 | */ |
| 169 | cmdline = (char*)malloc((PATH_MAX > pageSize ? PATH_MAX : pageSize) + 1); |
| 170 | if (cmdline == NULL) { |
| 171 | break; |
| 172 | } |
| 173 | |
| 174 | /* |
| 175 | * On Linux, the full path to the executable command is the link in |
| 176 | * /proc/<pid>/exe. But it is only readable for processes we own. |
| 177 | */ |
| 178 | snprintf(fn, sizeof fn, "/proc/%d/exe" , pid); |
| 179 | if ((cmdlen = readlink(fn, cmdline, PATH_MAX)) > 0) { |
| 180 | // null terminate and create String to store for command |
| 181 | cmdline[cmdlen] = '\0'; |
| 182 | cmdexe = JNU_NewStringPlatform(env, cmdline); |
| 183 | (*env)->ExceptionClear(env); // unconditionally clear any exception |
| 184 | } |
| 185 | |
| 186 | /* |
| 187 | * The command-line arguments appear as a set of strings separated by |
| 188 | * null bytes ('\0'), with a further null byte after the last |
| 189 | * string. The last string is only null terminated if the whole command |
| 190 | * line is not exceeding (PAGE_SIZE - 1) characters. |
| 191 | */ |
| 192 | cmdlen = 0; |
| 193 | s = cmdline; |
| 194 | while ((count = read(fd, s, pageSize - cmdlen)) > 0) { |
| 195 | cmdlen += count; |
| 196 | s += count; |
| 197 | } |
| 198 | if (count < 0) { |
| 199 | break; |
| 200 | } |
| 201 | // We have to null-terminate because the process may have changed argv[] |
| 202 | // or because the content in /proc/<pid>/cmdline is truncated. |
| 203 | cmdline[cmdlen] = '\0'; |
| 204 | if (cmdlen == pageSize && cmdline[pageSize - 1] != '\0') { |
| 205 | truncated = 1; |
| 206 | } else if (cmdlen == 0) { |
| 207 | // /proc/<pid>/cmdline was empty. This usually happens for kernel processes |
| 208 | // like '[kthreadd]'. We could try to read /proc/<pid>/comm in the future. |
| 209 | } |
| 210 | if (cmdlen > 0 && (cmdexe == NULL || truncated)) { |
| 211 | // We have no exact command or the arguments are truncated. |
| 212 | // In this case we save the command line from /proc/<pid>/cmdline. |
| 213 | args = (char*)malloc(pageSize + 1); |
| 214 | if (args != NULL) { |
| 215 | memcpy(args, cmdline, cmdlen + 1); |
| 216 | for (i = 0; i < cmdlen; i++) { |
| 217 | if (args[i] == '\0') { |
| 218 | args[i] = ' '; |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | i = 0; |
| 224 | if (!truncated) { |
| 225 | // Count the arguments |
| 226 | cmdEnd = &cmdline[cmdlen]; |
| 227 | for (s = cmdline; *s != '\0' && (s < cmdEnd); i++) { |
| 228 | s += strnlen(s, (cmdEnd - s)) + 1; |
| 229 | } |
| 230 | } |
| 231 | unix_fillArgArray(env, jinfo, i, cmdline, cmdEnd, cmdexe, args); |
| 232 | } while (0); |
| 233 | |
| 234 | if (cmdline != NULL) { |
| 235 | free(cmdline); |
| 236 | } |
| 237 | if (args != NULL) { |
| 238 | free(args); |
| 239 | } |
| 240 | if (fd >= 0) { |
| 241 | close(fd); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * Read the boottime from /proc/stat. |
| 247 | */ |
| 248 | static long long getBoottime(JNIEnv *env) { |
| 249 | FILE *fp; |
| 250 | char *line = NULL; |
| 251 | size_t len = 0; |
| 252 | long long bootTime = 0; |
| 253 | |
| 254 | fp = fopen("/proc/stat" , "r" ); |
| 255 | if (fp == NULL) { |
| 256 | return -1; |
| 257 | } |
| 258 | |
| 259 | while (getline(&line, &len, fp) != -1) { |
| 260 | if (sscanf(line, "btime %llu" , &bootTime) == 1) { |
| 261 | break; |
| 262 | } |
| 263 | } |
| 264 | free(line); |
| 265 | |
| 266 | if (fp != 0) { |
| 267 | fclose(fp); |
| 268 | } |
| 269 | |
| 270 | return bootTime * 1000; |
| 271 | } |
| 272 | |