Skip to content

Commit

Permalink
unix implementation of ProcessHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
wagyourtail committed Aug 20, 2024
1 parent a818ae0 commit 0d9386f
Show file tree
Hide file tree
Showing 9 changed files with 387 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package xyz.wagyourtail.jvmdg.j10.stub.java_base;

import xyz.wagyourtail.jvmdg.version.Stub;

import java.lang.management.RuntimeMXBean;

public class J_L_M_RuntimeMXBean {

@Stub
public static long getPid(RuntimeMXBean bean) {
String name = bean.getName();
name = name.substring(0, name.indexOf('@'));
return Long.parseLong(name);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package xyz.wagyourtail.jvmdg.j9.intl;

import org.jetbrains.annotations.NotNull;
import xyz.wagyourtail.jvmdg.j9.stub.java_base.J_L_ProcessHandle;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchService;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

public class UnixProcessHandle implements J_L_ProcessHandle {
private final long pid;
private String[] pidInfo;
private final String[] cmdline;

public UnixProcessHandle(long pid) {
this.pid = pid;
pidInfo = readPidInfo();
cmdline = readCmdLine();
}

public static boolean isUnix() {
return File.pathSeparatorChar == ':';
}

/**
* <a href="https://man7.org/linux/man-pages/man5/proc_pid_stat.5.html">/proc/&lt;pid&gt;/stat</a>
*/
private String[] readPidInfo() {
Path pth = Paths.get("/proc/" + pid + "/stat");
if (Files.isReadable(pth)) {
try {
pidInfo = new String(Files.readAllBytes(pth)).split(" ");
} catch (IOException e) {
pidInfo = null;
}
}
return pidInfo;
}

/**
* <a href="https://man7.org/linux/man-pages/man5/proc_pid_cmdline.5.html">/proc/&lt;pid&gt;/cmdline</a>
*/
private String[] readCmdLine() {
Path pth = Paths.get("/proc/" + pid + "/cmdline");
if (Files.isReadable(pth)) {
try {
return new String(Files.readAllBytes(pth)).split("\0");
} catch (IOException e) {
return null;
}
}
return null;
}

@Override
public long pid() {
return pid;
}

@Override
public Optional<J_L_ProcessHandle> parent() {
String[] info = readPidInfo();
if (info != null) {
return Optional.of(new UnixProcessHandle(Long.parseLong(info[3])));
}
return Optional.empty();
}

@Override
public Stream<J_L_ProcessHandle> children() {
Path pth = Paths.get("/proc/" + pid + "/task");
try(Stream<Path> stream = Files.list(pth)) {
return Stream.of(stream.toArray(Path[]::new)).flatMap(e -> {
try {
String s = new String(Files.readAllBytes(e.resolve("children")));
if (s.isEmpty()) {
return Stream.empty();
}
return Arrays.stream(s.split(" "));
} catch (IOException ex) {
return Stream.empty();
}
}).mapToLong(Long::parseLong).mapToObj(UnixProcessHandle::new);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public Stream<J_L_ProcessHandle> descendants() {
return children().flatMap(e -> Stream.concat(Stream.of(e), e.descendants()));
}

@Override
public Info info() {
return new Info() {
@Override
public Optional<String> command() {
if (cmdline != null) {
return Optional.ofNullable(cmdline[0]);
}
return Optional.empty();
}

@Override
public Optional<String> commandLine() {
if (cmdline != null) {
return Optional.of(String.join(" ", cmdline));
}
return Optional.empty();
}

@Override
public Optional<String[]> arguments() {
if (cmdline != null) {
String[] args = new String[cmdline.length - 1];
System.arraycopy(cmdline, 1, args, 0, cmdline.length - 1);
return Optional.of(args);
}
return Optional.empty();
}

@Override
public Optional<Instant> startInstant() {
String[] info = readPidInfo();
if (info != null) {
return Optional.of(Instant.ofEpochMilli(Long.parseLong(info[22])));
}
return Optional.empty();
}

@Override
public Optional<Duration> totalCpuDuration() {
String[] info = readPidInfo();
if (info != null) {
return Optional.of(Duration.ofMillis(Long.parseLong(info[14])));
}
return Optional.empty();
}

@Override
public Optional<String> user() {
try {
return Optional.of(Files.getOwner(Paths.get("/proc/" + pid + "/cmdline")).getName());
} catch (IOException e) {
return Optional.empty();
}
}
};
}

@Override
public CompletableFuture<J_L_ProcessHandle> onExit() {
return CompletableFuture.supplyAsync(() -> {
try (WatchService ws = FileSystems.getDefault().newWatchService()) {
Path pth = Paths.get("/proc/" + pid + "/status");
pth.register(ws, StandardWatchEventKinds.ENTRY_DELETE);
ws.take();
return this;
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}

@Override
public boolean supportsNormalTermination() {
return true;
}

@Override
public boolean destroy() {
ProcessBuilder pb = new ProcessBuilder("kill", Long.toString(pid));
try {
Process p = pb.start();
p.waitFor();
return p.exitValue() == 0;
} catch (IOException | InterruptedException e) {
return false;
}
}

@Override
public boolean destroyForcibly() {
ProcessBuilder pb = new ProcessBuilder("kill", "-9", Long.toString(pid));
try {
Process p = pb.start();
p.waitFor();
return p.exitValue() == 0;
} catch (IOException | InterruptedException e) {
return false;
}
}

@Override
public boolean isAlive() {
return Files.exists(Paths.get("/proc/" + pid + "/status"));
}

@Override
public int compareTo(@NotNull J_L_ProcessHandle other) {
return Long.compare(pid, other.pid());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package xyz.wagyourtail.jvmdg.j9.stub.java_base;

import org.jetbrains.annotations.NotNull;
import sun.management.ManagementFactoryHelper;
import sun.management.VMManagement;
import xyz.wagyourtail.jvmdg.exc.MissingStubError;
import xyz.wagyourtail.jvmdg.j9.intl.UnixProcessHandle;
import xyz.wagyourtail.jvmdg.util.Utils;
import xyz.wagyourtail.jvmdg.version.Adapter;

import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

/**
* Implementation of ProcessHandle is very incomplete, as can only get this kind of info on the
* current process without access to natives.
* <p>
* If you know some funny sun classes or something to get other processes, lmk
*/
@Adapter("java/lang/ProcessHandle")
public interface J_L_ProcessHandle extends Comparable<J_L_ProcessHandle> {
RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean();

static Optional<J_L_ProcessHandle> of(long pid) {
if (pid == current().pid()) {
return Optional.of(current());
}
throw new UnsupportedOperationException("Getting arbitrary PID process handles is not supported");
}

static J_L_ProcessHandle current() {
String pidName = bean.getName();
long pid = Long.parseLong(pidName.substring(0, pidName.indexOf('@')));
if (UnixProcessHandle.isUnix()) {
return new UnixProcessHandle(pid) {
@Override
public CompletableFuture<J_L_ProcessHandle> onExit() {
throw new IllegalStateException();
}
};
}
throw MissingStubError.create();
}

static Stream<J_L_ProcessHandle> allProcesses() {
return Stream.of(current());
}

long pid();

Optional<J_L_ProcessHandle> parent();

Stream<J_L_ProcessHandle> children();

Stream<J_L_ProcessHandle> descendants();

Info info();

CompletableFuture<J_L_ProcessHandle> onExit();

boolean supportsNormalTermination();

boolean destroy();

boolean destroyForcibly();

boolean isAlive();

int hashCode();

boolean equals(Object obj);

@Override
int compareTo(@NotNull J_L_ProcessHandle other);

@Adapter("java/lang/ProcessHandle$Info")
interface Info {
Optional<String> command();

Optional<String> commandLine();

Optional<String[]> arguments();

Optional<Instant> startInstant();

Optional<Duration> totalCpuDuration();

Optional<String> user();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void init() {
// ButtonModel

// -- java.management --
// RuntimeMXBean
stub(J_L_M_RuntimeMXBean.class);
// ThreadMXBean

// -- jdk.compiler --
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void init() {
stub(J_L_ModuleLayer.class);
// Process
// ProcessBuilder
// ProcessHandle
stub(J_L_ProcessHandle.class);
// ProcessHandleImpl
stub(J_L_Runtime.class);
stub(J_L_Runtime.Version.class);
Expand All @@ -48,18 +48,6 @@ public void init() {
stub(J_L_StrictMath.class);
// String -- handled by CharSequence
stub(J_L_System.class);
// TODO: this properly
// ClassReplacer.registerReplace(
// JavaVersion.VERSION_1_9,
// "Ljava/lang/System\$Logger;",
// "Ljava/util/logging/Logger;"
// )
// ClassReplacer.registerReplace(
// JavaVersion.VERSION_1_9,
// "Ljava/lang/System\$Logger\$Level;",
// "Ljava/util/logging/Level;"
// )
// System$LoggerFinder
stub(J_L_Thread.class);
// ElementType
// MethodHandle
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/xyz/wagyourtail/jvmdg/cli/Arguments.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ public Map<String, List<String[]>> read(List<String> args, boolean allowExtra) {
continue outer;
}
}
for (Set<String> strings : children.keySet()) {
if (strings.contains("")) {
args.add(0, arg);
ret.put("", Collections.singletonList(args.toArray(new String[0])));
args.clear();
continue outer;
}
}
if (allowExtra) {
args.add(0, arg);
break;
Expand Down
Loading

0 comments on commit 0d9386f

Please sign in to comment.