1 /*
2 * Copyright (c) 2014, 2024, 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 package java.lang;
26
27 import jdk.internal.misc.InnocuousThread;
28
29 import java.lang.annotation.Native;
30 import java.security.AccessController;
31 import java.security.PrivilegedAction;
32 import java.time.Duration;
33 import java.time.Instant;
34 import java.util.Arrays;
35 import java.util.Optional;
36 import java.util.concurrent.CompletableFuture;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.ConcurrentMap;
39 import java.util.concurrent.Executor;
40 import java.util.concurrent.Executors;
41 import java.util.concurrent.ThreadFactory;
42 import java.util.concurrent.ThreadLocalRandom;
43 import java.util.stream.IntStream;
44 import java.util.stream.Stream;
45
46 /**
47 * ProcessHandleImpl is the implementation of ProcessHandle.
48 *
49 * @see Process
50 * @since 9
51 */
52 @jdk.internal.ValueBased
53 final class ProcessHandleImpl implements ProcessHandle {
54 /**
55 * Default size of stack for reaper processes.
56 */
57 private static final long REAPER_DEFAULT_STACKSIZE = 128 * 1024;
58
59 /**
60 * Return value from waitForProcessExit0 indicating the process is not a child.
61 */
62 @Native
63 private static final int NOT_A_CHILD = -2;
64
65 /**
66 * Cache the ProcessHandle of this process.
67 */
68 private static final ProcessHandleImpl current;
69
70 /**
71 * Map of pids to ExitCompletions.
72 */
73 private static final ConcurrentMap<Long, ExitCompletion>
74 completions = new ConcurrentHashMap<>();
75
76 static {
77 initNative();
78 long pid = getCurrentPid0();
79 current = new ProcessHandleImpl(pid, isAlive0(pid));
80 }
81
82 private static native void initNative();
83
84 /**
85 * The thread pool of "process reaper" daemon threads.
86 */
87 private static final Executor processReaperExecutor = initReaper();
88
89 private static Executor initReaper() {
90 // Initialize ThreadLocalRandom and ValueObjectMethods now to avoid using the smaller stack
91 // of the processReaper threads.
92 ThreadLocalRandom.current();
93 try {
94 Class.forName("java.lang.runtime.ValueObjectMethods$MethodHandleBuilder", true, null);
95 } catch (ClassNotFoundException cnfe) {
96 throw new InternalError("unable to initialize ValueObjectMethodds", cnfe);
97 }
98 // For a debug build, the stack shadow zone is larger;
99 // Increase the total stack size to avoid potential stack overflow.
100 int debugDelta = "release".equals(System.getProperty("jdk.debug")) ? 0 : (4*4096);
101 final long stackSize = Boolean.getBoolean("jdk.lang.processReaperUseDefaultStackSize")
102 ? 0 : REAPER_DEFAULT_STACKSIZE + debugDelta;
103
104 ThreadFactory threadFactory = grimReaper -> {
105 Thread t = InnocuousThread.newSystemThread("process reaper", grimReaper,
106 stackSize, Thread.MAX_PRIORITY);
107 t.setDaemon(true);
108 return t;
109 };
110
111 return Executors.newCachedThreadPool(threadFactory);
112 }
113
114 private static class ExitCompletion extends CompletableFuture<Integer> {
115 final boolean isReaping;
116
117 ExitCompletion(boolean isReaping) {
118 this.isReaping = isReaping;
119 }
120 }
121
122 /**
123 * Returns a CompletableFuture that completes with process exit status when
124 * the process completes.
125 *
126 * @param shouldReap true if the exit value should be reaped
127 */
128 static CompletableFuture<Integer> completion(long pid, boolean shouldReap) {
129 // check canonicalizing cache 1st
130 ExitCompletion completion = completions.get(pid);
131 // re-try until we get a completion that shouldReap => isReaping
132 while (completion == null || (shouldReap && !completion.isReaping)) {
133 ExitCompletion newCompletion = new ExitCompletion(shouldReap);
134 if (completion == null) {
135 completion = completions.putIfAbsent(pid, newCompletion);
136 } else {
137 completion = completions.replace(pid, completion, newCompletion)
138 ? null : completions.get(pid);
139 }
140 if (completion == null) {
141 // newCompletion has just been installed successfully
142 completion = newCompletion;
143 // spawn a thread to wait for and deliver the exit value
144 processReaperExecutor.execute(new Runnable() {
145 // Use inner class to avoid lambda stack overhead
146 public void run() {
147 Thread t = Thread.currentThread();
148 String threadName = t.getName();
149 t.setName("process reaper (pid " + pid + ")");
150 try {
151 int exitValue = waitForProcessExit0(pid, shouldReap);
152 if (exitValue == NOT_A_CHILD) {
153 // pid not alive or not a child of this process
154 // If it is alive wait for it to terminate
155 long sleep = 300; // initial milliseconds to sleep
156 int incr = 30; // increment to the sleep time
157
158 long startTime = isAlive0(pid);
159 long origStart = startTime;
160 while (startTime >= 0) {
161 try {
162 Thread.sleep(Math.min(sleep, 5000L)); // no more than 5 sec
163 sleep += incr;
164 } catch (InterruptedException ie) {
165 // ignore and retry
166 }
167 startTime = isAlive0(pid); // recheck if it is alive
168 if (startTime > 0 && origStart > 0 && startTime != origStart) {
169 // start time changed (and is not zero), pid is not the same process
170 break;
171 }
172 }
173 exitValue = 0;
174 }
175 newCompletion.complete(exitValue);
176 // remove from cache afterwards
177 completions.remove(pid, newCompletion);
178 } finally {
179 // Restore thread name
180 t.setName(threadName);
181 }
182 }
183 });
184 }
185 }
186 return completion;
187 }
188
189 @Override
190 public CompletableFuture<ProcessHandle> onExit() {
191 if (this.equals(current)) {
192 throw new IllegalStateException("onExit for current process not allowed");
193 }
194
195 return ProcessHandleImpl.completion(pid(), false)
196 .handleAsync((exitStatus, unusedThrowable) -> this);
197 }
198
199 /**
200 * Wait for the process to exit, return the value.
201 * Conditionally reap the value if requested
202 * @param pid the processId
203 * @param reapvalue if true, the value is retrieved,
204 * else return the value and leave the process waitable
205 *
206 * @return the value or -1 if an error occurs
207 */
208 private static native int waitForProcessExit0(long pid, boolean reapvalue);
209
210 /**
211 * The pid of this ProcessHandle.
212 */
213 private final long pid;
214
215 /**
216 * The start time of this process.
217 * If STARTTIME_ANY, the start time of the process is not available from the os.
218 * If greater than zero, the start time of the process.
219 */
220 private final long startTime;
221
222 /* The start time should match any value.
223 * Typically, this is because the OS can not supply it.
224 * The process is known to exist but not the exact start time.
225 */
226 private static final long STARTTIME_ANY = 0L;
227
228 /* The start time of a Process that does not exist. */
229 private static final long STARTTIME_PROCESS_UNKNOWN = -1;
230
231 /**
232 * Private constructor. Instances are created by the {@code get(long)} factory.
233 * @param pid the pid for this instance
234 */
235 private ProcessHandleImpl(long pid, long startTime) {
236 this.pid = pid;
237 this.startTime = startTime;
238 }
239
240 /**
241 * Returns a ProcessHandle for an existing native process.
242 *
243 * @param pid the native process identifier
244 * @return The ProcessHandle for the pid if the process is alive;
245 * or {@code null} if the process ID does not exist in the native system.
246 */
247 static Optional<ProcessHandle> get(long pid) {
248 long start = isAlive0(pid);
249 return (start >= 0)
250 ? Optional.of(new ProcessHandleImpl(pid, start))
251 : Optional.empty();
252 }
253
254 /**
255 * Returns a ProcessHandle for an existing native process known to be alive.
256 * The startTime of the process is retrieved and stored in the ProcessHandle.
257 * It does not perform a security check since it is called from ProcessImpl.
258 * @param pid of the known to exist process
259 * @return a ProcessHandle corresponding to an existing Process instance
260 */
261 static ProcessHandleImpl getInternal(long pid) {
262 return new ProcessHandleImpl(pid, isAlive0(pid));
263 }
264
265 /**
266 * Returns the native process ID.
267 * A {@code long} is used to be able to fit the system specific binary values
268 * for the process.
269 *
270 * @return the native process ID
271 */
272 @Override
273 public long pid() {
274 return pid;
275 }
276
277 /**
278 * Returns the ProcessHandle for the current native process.
279 *
280 * @return The ProcessHandle for the OS process.
281 */
282 public static ProcessHandleImpl current() {
283 return current;
284 }
285
286 /**
287 * Return the pid of the current process.
288 *
289 * @return the pid of the current process
290 */
291 private static native long getCurrentPid0();
292
293 /**
294 * Returns a ProcessHandle for the parent process.
295 *
296 * @return a ProcessHandle of the parent process; {@code null} is returned
297 * if the child process does not have a parent
298 */
299 public Optional<ProcessHandle> parent() {
300 long ppid = parent0(pid, startTime);
301 if (ppid <= 0) {
302 return Optional.empty();
303 }
304 return get(ppid);
305 }
306
307 /**
308 * Returns the parent of the native pid argument.
309 *
310 * @param pid the process id
311 * @param startTime the startTime of the process
312 * @return the parent of the native pid; if any, otherwise -1
313 */
314 private static native long parent0(long pid, long startTime);
315
316 /**
317 * Returns the number of pids filled in to the array.
318 * @param pid if {@code pid} equals zero, then all known processes are returned;
319 * otherwise only direct child process pids are returned
320 * @param pids an allocated long array to receive the pids
321 * @param ppids an allocated long array to receive the parent pids; may be null
322 * @param starttimes an allocated long array to receive the child start times; may be null
323 * @return if greater than or equal to zero is the number of pids in the array;
324 * if greater than the length of the arrays, the arrays are too small
325 */
326 private static native int getProcessPids0(long pid, long[] pids,
327 long[] ppids, long[] starttimes);
328
329 /**
330 * Destroy the process for this ProcessHandle.
331 * The native code checks the start time before sending the termination request.
332 *
333 * @param force {@code true} if the process should be terminated forcibly;
334 * else {@code false} for a normal termination
335 */
336 boolean destroyProcess(boolean force) {
337 if (this.equals(current)) {
338 throw new IllegalStateException("destroy of current process not allowed");
339 }
340 return destroy0(pid, startTime, force);
341 }
342
343 /**
344 * Signal the process to terminate.
345 * The process is signaled only if its start time matches the known start time.
346 *
347 * @param pid process id to kill
348 * @param startTime the start time of the process
349 * @param forcibly true to forcibly terminate (SIGKILL vs SIGTERM)
350 * @return true if the process was signaled without error; false otherwise
351 */
352 private static native boolean destroy0(long pid, long startTime, boolean forcibly);
353
354 @Override
355 public boolean destroy() {
356 return destroyProcess(false);
357 }
358
359 @Override
360 public boolean destroyForcibly() {
361 return destroyProcess(true);
362 }
363
364
365 @Override
366 public boolean supportsNormalTermination() {
367 return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
368 }
369
370 /**
371 * Tests whether the process represented by this {@code ProcessHandle} is alive.
372 *
373 * @return {@code true} if the process represented by this
374 * {@code ProcessHandle} object has not yet terminated.
375 * @since 9
376 */
377 @Override
378 public boolean isAlive() {
379 long start = isAlive0(pid);
380 return (start >= 0 && (start == startTime || start == 0 || startTime == 0));
381 }
382
383 /**
384 * Returns the process start time depending on whether the pid is alive.
385 * This must not reap the exitValue.
386 *
387 * @param pid the pid to check
388 * @return the start time in milliseconds since 1970,
389 * 0 if the start time cannot be determined,
390 * -1 if the pid does not exist.
391 */
392 private static native long isAlive0(long pid);
393
394 @Override
395 public Stream<ProcessHandle> children() {
396 // The native OS code selects based on matching the requested parent pid.
397 // If the original parent exits, the pid may have been re-used for
398 // this newer process.
399 // Processes started by the original parent (now dead) will all have
400 // start times less than the start of this newer parent.
401 // Processes started by this newer parent will have start times equal
402 // or after this parent.
403 return children(pid).filter(ph -> startTime <= ((ProcessHandleImpl)ph).startTime);
404 }
405
406 /**
407 * Returns a Stream of the children of a process or all processes.
408 *
409 * @param pid the pid of the process for which to find the children;
410 * 0 for all processes
411 * @return a stream of ProcessHandles
412 */
413 static Stream<ProcessHandle> children(long pid) {
414 int size = 100;
415 long[] childpids = null;
416 long[] starttimes = null;
417 while (childpids == null || size > childpids.length) {
418 childpids = new long[size];
419 starttimes = new long[size];
420 size = getProcessPids0(pid, childpids, null, starttimes);
421 }
422
423 final long[] cpids = childpids;
424 final long[] stimes = starttimes;
425 return IntStream.range(0, size).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i]));
426 }
427
428 @Override
429 public Stream<ProcessHandle> descendants() {
430 int size = 100;
431 long[] pids = null;
432 long[] ppids = null;
433 long[] starttimes = null;
434 while (pids == null || size > pids.length) {
435 pids = new long[size];
436 ppids = new long[size];
437 starttimes = new long[size];
438 size = getProcessPids0(0, pids, ppids, starttimes);
439 }
440
441 int next = 0; // index of next process to check
442 int count = -1; // count of subprocesses scanned
443 long ppid = pid; // start looking for this parent
444 long ppStart = 0;
445 // Find the start time of the parent
446 for (int i = 0; i < size; i++) {
447 if (pids[i] == ppid) {
448 ppStart = starttimes[i];
449 break;
450 }
451 }
452 do {
453 // Scan from next to size looking for ppid with child start time
454 // the same or later than the parent.
455 // If found, exchange it with index next
456 for (int i = next; i < size; i++) {
457 if (ppids[i] == ppid &&
458 ppStart <= starttimes[i]) {
459 swap(pids, i, next);
460 swap(ppids, i, next);
461 swap(starttimes, i, next);
462 next++;
463 }
464 }
465 ppid = pids[++count]; // pick up the next pid to scan for
466 ppStart = starttimes[count]; // and its start time
467 } while (count < next);
468
469 final long[] cpids = pids;
470 final long[] stimes = starttimes;
471 return IntStream.range(0, count).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i]));
472 }
473
474 // Swap two elements in an array
475 private static void swap(long[] array, int x, int y) {
476 long v = array[x];
477 array[x] = array[y];
478 array[y] = v;
479 }
480
481 @Override
482 public ProcessHandle.Info info() {
483 return ProcessHandleImpl.Info.info(pid, startTime);
484 }
485
486 @Override
487 public int compareTo(ProcessHandle other) {
488 return Long.compare(pid, ((ProcessHandleImpl) other).pid);
489 }
490
491 @Override
492 public String toString() {
493 return Long.toString(pid);
494 }
495
496 @Override
497 public int hashCode() {
498 return Long.hashCode(pid);
499 }
500
501 @Override
502 public boolean equals(Object obj) {
503 if (this == obj) {
504 return true;
505 }
506 return (obj instanceof ProcessHandleImpl other)
507 && (pid == other.pid)
508 && (startTime == other.startTime || startTime == 0 || other.startTime == 0);
509 }
510
511 /**
512 * Implementation of ProcessHandle.Info.
513 * Information snapshot about a process.
514 * The attributes of a process vary by operating system and are not available
515 * in all implementations. Additionally, information about other processes
516 * is limited by the operating system privileges of the process making the request.
517 * If a value is not available, either a {@code null} or {@code -1} is stored.
518 * The accessor methods return {@code null} if the value is not available.
519 */
520 static class Info implements ProcessHandle.Info {
521 static {
522 initIDs();
523 }
524
525 /**
526 * Initialization of JNI fieldIDs.
527 */
528 private static native void initIDs();
529
530 /**
531 * Fill in this Info instance with information about the native process.
532 * If values are not available the native code does not modify the field.
533 * @param pid of the native process
534 */
535 private native void info0(long pid);
536
537 String command;
538 String commandLine;
539 String[] arguments;
540 long startTime;
541 long totalTime;
542 String user;
543
544 Info() {
545 command = null;
546 commandLine = null;
547 arguments = null;
548 startTime = -1L;
549 totalTime = -1L;
550 user = null;
551 }
552
553 /**
554 * Returns the Info object with the fields from the process.
555 * Whatever fields are provided by native are returned.
556 * If the startTime of the process does not match the provided
557 * startTime then an empty Info is returned.
558 *
559 * @param pid the native process identifier
560 * @param startTime the startTime of the process being queried
561 * @return ProcessHandle.Info non-null; individual fields may be null
562 * or -1 if not available.
563 */
564 public static ProcessHandle.Info info(long pid, long startTime) {
565 Info info = new Info();
566 info.info0(pid);
567 if (startTime != info.startTime) {
568 info.command = null;
569 info.arguments = null;
570 info.startTime = -1L;
571 info.totalTime = -1L;
572 info.user = null;
573 }
574 return info;
575 }
576
577 @Override
578 public Optional<String> command() {
579 return Optional.ofNullable(command);
580 }
581
582 @Override
583 public Optional<String> commandLine() {
584 if (command != null && arguments != null) {
585 return Optional.of(command + " " + String.join(" ", arguments));
586 } else {
587 return Optional.ofNullable(commandLine);
588 }
589 }
590
591 @Override
592 public Optional<String[]> arguments() {
593 return Optional.ofNullable(arguments);
594 }
595
596 @Override
597 public Optional<Instant> startInstant() {
598 return (startTime > 0)
599 ? Optional.of(Instant.ofEpochMilli(startTime))
600 : Optional.empty();
601 }
602
603 @Override
604 public Optional<Duration> totalCpuDuration() {
605 return (totalTime != -1)
606 ? Optional.of(Duration.ofNanos(totalTime))
607 : Optional.empty();
608 }
609
610 @Override
611 public Optional<String> user() {
612 return Optional.ofNullable(user);
613 }
614
615 @Override
616 public String toString() {
617 StringBuilder sb = new StringBuilder(60);
618 sb.append('[');
619 if (user != null) {
620 sb.append("user: ");
621 sb.append(user());
622 }
623 if (command != null) {
624 if (sb.length() > 1) sb.append(", ");
625 sb.append("cmd: ");
626 sb.append(command);
627 }
628 if (arguments != null && arguments.length > 0) {
629 if (sb.length() > 1) sb.append(", ");
630 sb.append("args: ");
631 sb.append(Arrays.toString(arguments));
632 }
633 if (commandLine != null) {
634 if (sb.length() > 1) sb.append(", ");
635 sb.append("cmdLine: ");
636 sb.append(commandLine);
637 }
638 if (startTime > 0) {
639 if (sb.length() > 1) sb.append(", ");
640 sb.append("startTime: ");
641 sb.append(startInstant());
642 }
643 if (totalTime != -1) {
644 if (sb.length() > 1) sb.append(", ");
645 sb.append("totalTime: ");
646 sb.append(totalCpuDuration().toString());
647 }
648 sb.append(']');
649 return sb.toString();
650 }
651 }
652 }