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