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 | |