From 1d5b01f3ce395b3bdff010a4fcc69b639f85a073 Mon Sep 17 00:00:00 2001
From: George Gastaldi <gegastaldi@gmail.com>
Date: Mon, 8 Mar 2021 11:14:34 -0300
Subject: [PATCH] Introduce Process.getAllProcess

Fixes #79
---
 .../common/os/GetAllProcessesInfoAction.java  | 117 ++++++++++++++++++
 .../common/os/GetProcessInfoAction.java       |   1 +
 .../java/io/smallrye/common/os/Process.java   |  31 ++++-
 .../io/smallrye/common/os/ProcessInfo.java    |  22 ++++
 .../common/os/GetAllProcessesInfoAction.java  |  15 +++
 .../io/smallrye/common/os/ProcessTest.java    |   7 ++
 6 files changed, 187 insertions(+), 6 deletions(-)
 create mode 100644 os/src/main/java/io/smallrye/common/os/GetAllProcessesInfoAction.java
 create mode 100644 os/src/main/java/io/smallrye/common/os/ProcessInfo.java
 create mode 100644 os/src/main/java9/io/smallrye/common/os/GetAllProcessesInfoAction.java

diff --git a/os/src/main/java/io/smallrye/common/os/GetAllProcessesInfoAction.java b/os/src/main/java/io/smallrye/common/os/GetAllProcessesInfoAction.java
new file mode 100644
index 00000000..9877033a
--- /dev/null
+++ b/os/src/main/java/io/smallrye/common/os/GetAllProcessesInfoAction.java
@@ -0,0 +1,117 @@
+package io.smallrye.common.os;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+final class GetAllProcessesInfoAction implements PrivilegedAction<List<ProcessInfo>> {
+
+    private final static Predicate<String> IS_NUMBER = Pattern.compile("\\d+").asPredicate();
+
+    @Override
+    public List<ProcessInfo> run() {
+        // The ProcessHandle API does not exist, so we have to rely on external processes to get this information
+        switch (OS.current()) {
+            case LINUX:
+                return readLinuxProcesses();
+            case MAC:
+                return readMacProcesses();
+            case WINDOWS:
+                return readWindowsProcesses();
+            default:
+                throw new UnsupportedOperationException(
+                        "Listing all processes is not supported in JDK 8 in " + OS.current().name());
+        }
+    }
+
+    private List<ProcessInfo> readLinuxProcesses() {
+        List<ProcessInfo> processes = new ArrayList<>();
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get("/proc"))) {
+            for (Path procPath : stream) {
+                String name = procPath.getFileName().toString();
+                if (IS_NUMBER.test(name)) {
+                    long pid = Long.parseLong(name);
+                    try (BufferedReader reader = Files
+                            .newBufferedReader(procPath.resolve("cmdline"), StandardCharsets.UTF_8)) {
+                        String line = reader.readLine();
+                        if (line != null) {
+                            int idx = line.indexOf(0);
+                            String cmdLine = idx == -1 ? line : line.substring(0, idx);
+                            processes.add(new ProcessInfo(pid, cmdLine));
+                        }
+                    }
+                }
+            }
+        } catch (IOException ignored) {
+            // ignore
+        }
+        return processes;
+    }
+
+    private List<ProcessInfo> readMacProcesses() {
+        List<ProcessInfo> processes = new ArrayList<>();
+        java.lang.Process process = null;
+        try {
+            //    PID CMD
+            //      1 /usr/lib/systemd/systemd --switched-root --system --deserialize 34
+            process = new ProcessBuilder("ps", "-ax", "-o", "pid,cmd").start();
+            try (Scanner scanner = new Scanner(process.getInputStream())) {
+                if (scanner.hasNextLine()) {
+                    scanner.nextLine();
+                }
+                while (scanner.hasNextLine()) {
+                    String line = scanner.nextLine();
+                    int separator = line.indexOf(" ");
+                    long pid = Long.parseLong(line.substring(0, separator));
+                    String cmd = line.substring(separator + 1);
+                    processes.add(new ProcessInfo(pid, cmd));
+                }
+            }
+        } catch (IOException e) {
+            // ignored
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+        return processes;
+    }
+
+    private List<ProcessInfo> readWindowsProcesses() {
+        List<ProcessInfo> processes = new ArrayList<>();
+        java.lang.Process process = null;
+        try {
+            process = new ProcessBuilder("tasklist.exe", "/fo", "csv", "/nh").start();
+            try (Scanner sc = new Scanner(process.getInputStream())) {
+                // Skip first line
+                if (sc.hasNextLine()) {
+                    sc.nextLine();
+                }
+                while (sc.hasNextLine()) {
+                    String line = sc.nextLine();
+                    String[] parts = line.split(",");
+                    String cmdLine = parts[0].substring(1).replaceFirst(".$", "");
+                    long pid = Long.parseLong(parts[1].substring(1).replaceFirst(".$", ""));
+                    processes.add(new ProcessInfo(pid, cmdLine));
+                }
+            }
+        } catch (IOException e) {
+            // ignored
+        } finally {
+            if (process != null) {
+                process.destroy();
+            }
+        }
+        return processes;
+    }
+}
diff --git a/os/src/main/java/io/smallrye/common/os/GetProcessInfoAction.java b/os/src/main/java/io/smallrye/common/os/GetProcessInfoAction.java
index 0148b722..e74b08ae 100644
--- a/os/src/main/java/io/smallrye/common/os/GetProcessInfoAction.java
+++ b/os/src/main/java/io/smallrye/common/os/GetProcessInfoAction.java
@@ -31,6 +31,7 @@ final class GetProcessInfoAction implements PrivilegedAction<Object[]> {
     GetProcessInfoAction() {
     }
 
+    @Override
     public Object[] run() {
         long pid = -1L;
         String processName = "<unknown>";
diff --git a/os/src/main/java/io/smallrye/common/os/Process.java b/os/src/main/java/io/smallrye/common/os/Process.java
index b9b102ef..dab005e7 100644
--- a/os/src/main/java/io/smallrye/common/os/Process.java
+++ b/os/src/main/java/io/smallrye/common/os/Process.java
@@ -20,19 +20,19 @@
 
 import static java.security.AccessController.doPrivileged;
 
+import java.util.List;
+
 /**
  * Utilities for getting information about the current process.
  *
  * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
  */
 public final class Process {
-    private static final long processId;
-    private static final String processName;
+    private static final ProcessInfo currentProcess;
 
     static {
         Object[] array = doPrivileged(new GetProcessInfoAction());
-        processId = (Long) array[0];
-        processName = (String) array[1];
+        currentProcess = new ProcessInfo((Long) array[0], (String) array[1]);
     }
 
     private Process() {
@@ -44,7 +44,7 @@ private Process() {
      * @return the process name (not {@code null})
      */
     public static String getProcessName() {
-        return processName;
+        return currentProcess.getCommand();
     }
 
     /**
@@ -54,6 +54,25 @@ public static String getProcessName() {
      * @return the ID of this process, or -1 if it cannot be determined
      */
     public static long getProcessId() {
-        return processId;
+        return currentProcess.getId();
+    }
+
+    /**
+     * Returns information about the current process
+     *
+     * @return the current process
+     */
+    public static ProcessInfo getCurrentProcess() {
+        return currentProcess;
+    }
+
+    /**
+     * Returns all the running processes.
+     *
+     * @return a list of all the running processes. May throw an exception if running on an unsupported JDK
+     * @throws UnsupportedOperationException if running on JDK 8
+     */
+    public static List<ProcessInfo> getAllProcesses() {
+        return doPrivileged(new GetAllProcessesInfoAction());
     }
 }
diff --git a/os/src/main/java/io/smallrye/common/os/ProcessInfo.java b/os/src/main/java/io/smallrye/common/os/ProcessInfo.java
new file mode 100644
index 00000000..fde7b6d0
--- /dev/null
+++ b/os/src/main/java/io/smallrye/common/os/ProcessInfo.java
@@ -0,0 +1,22 @@
+package io.smallrye.common.os;
+
+/**
+ * Returns information about a Process
+ */
+public class ProcessInfo {
+    private final long id;
+    private final String command;
+
+    public ProcessInfo(long id, String command) {
+        this.id = id;
+        this.command = command;
+    }
+
+    public long getId() {
+        return id;
+    }
+
+    public String getCommand() {
+        return command;
+    }
+}
diff --git a/os/src/main/java9/io/smallrye/common/os/GetAllProcessesInfoAction.java b/os/src/main/java9/io/smallrye/common/os/GetAllProcessesInfoAction.java
new file mode 100644
index 00000000..d16de967
--- /dev/null
+++ b/os/src/main/java9/io/smallrye/common/os/GetAllProcessesInfoAction.java
@@ -0,0 +1,15 @@
+package io.smallrye.common.os;
+
+import java.security.PrivilegedAction;
+import java.util.List;
+import java.util.stream.Collectors;
+
+final class GetAllProcessesInfoAction implements PrivilegedAction<List<ProcessInfo>> {
+
+    @Override
+    public List<ProcessInfo> run() {
+        return ProcessHandle.allProcesses()
+                .map(processHandle -> new ProcessInfo(processHandle.pid(), processHandle.info().command().orElse(null)))
+                .collect(Collectors.toList());
+    }
+}
diff --git a/os/src/test/java/io/smallrye/common/os/ProcessTest.java b/os/src/test/java/io/smallrye/common/os/ProcessTest.java
index d3de07c0..c84571b5 100644
--- a/os/src/test/java/io/smallrye/common/os/ProcessTest.java
+++ b/os/src/test/java/io/smallrye/common/os/ProcessTest.java
@@ -1,5 +1,6 @@
 package io.smallrye.common.os;
 
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
@@ -11,6 +12,12 @@ class ProcessTest {
     public void testProcessInfo() {
         assertNotEquals(0L, Process.getProcessId());
         assertNotNull(Process.getProcessName());
+        assertNotNull(Process.getCurrentProcess());
+    }
+
+    @Test
+    public void testAllProcessInfo() {
+        assertFalse(Process.getAllProcesses().isEmpty());
     }
 
 }