From e5974140c7a71ba6dc4ea57b99a8b5618c1a423d Mon Sep 17 00:00:00 2001 From: Felix Marx <97086574+FelixMarxIBM@users.noreply.github.com> Date: Mon, 8 Jul 2024 17:54:04 +0200 Subject: [PATCH] AIX: add istat parser as stat is absent (#1668) --- .../net/bytebuddy/agent/VirtualMachine.java | 305 +++++++++++++++--- 1 file changed, 254 insertions(+), 51 deletions(-) diff --git a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java index 577bcb163f..905913629c 100644 --- a/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java +++ b/byte-buddy-agent/src/main/java/net/bytebuddy/agent/VirtualMachine.java @@ -33,6 +33,8 @@ import java.security.SecureRandom; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** *

@@ -2077,20 +2079,7 @@ class ForJnaPosixEnvironment implements Dispatcher { */ private final PosixLibrary library; - /** - * The maximum amount of attempts for checking the result of a foreign process. - */ - private final int attempts; - - /** - * The pause between two checks for another process to return. - */ - private final long pause; - - /** - * The time unit of the pause time. - */ - private final TimeUnit timeUnit; + private final PosixOwner posixOwner; /** * Creates a new connector for a POSIX enviornment using JNA. @@ -2101,9 +2090,13 @@ class ForJnaPosixEnvironment implements Dispatcher { */ @SuppressWarnings("deprecation") public ForJnaPosixEnvironment(int attempts, long pause, TimeUnit timeUnit) { - this.attempts = attempts; - this.pause = pause; - this.timeUnit = timeUnit; + if (Platform.isMac()) { + posixOwner = new PosixOwner.ForMacEnvironment(attempts, pause, timeUnit); + } else if (Platform.isAIX()) { + posixOwner = new PosixOwner.ForAixEnvironment(attempts, pause, timeUnit); + } else { + posixOwner = new PosixOwner.ForLinuxEnvironment(attempts, pause, timeUnit); + } library = Native.loadLibrary("c", PosixLibrary.class); } @@ -2147,40 +2140,7 @@ public boolean isExistingProcess(int processId) { */ @SuppressFBWarnings(value = "OS_OPEN_STREAM", justification = "The stream life-cycle is bound to its process.") public int getOwnerIdOf(File file) { - try { - // The binding for 'stat' is very platform dependant. To avoid the complexity of binding the correct method, - // stat is called as a separate command. This is less efficient but more portable. - Process process = Runtime.getRuntime().exec(new String[]{"stat", - Platform.isMac() ? "-f" : "-c", - "%u", - file.getAbsolutePath()}); - int attempts = this.attempts; - boolean exited = false; - String line = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")).readLine(); - do { - try { - if (process.exitValue() != 0) { - throw new IllegalStateException("Error while executing stat"); - } - exited = true; - break; - } catch (IllegalThreadStateException ignored) { - try { - Thread.sleep(timeUnit.toMillis(pause)); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - throw new IllegalStateException(exception); - } - } - } while (--attempts > 0); - if (!exited) { - process.destroy(); - throw new IllegalStateException("Command for stat did not exit in time"); - } - return Integer.parseInt(line); - } catch (IOException exception) { - throw new IllegalStateException("Unable to execute stat command", exception); - } + return posixOwner.getOwnerIdOf(file); } /** @@ -2385,6 +2345,249 @@ protected List getFieldOrder() { } } } + + protected interface PosixOwner { + /** + * Returns the user id of the owner of the supplied file. + * + * @param file The file for which to locate the owner. + * @return The owner id of the supplied file. + */ + int getOwnerIdOf(File file); + + class ForLinuxEnvironment implements PosixOwner { + + /** + * The maximum amount of attempts for checking the result of a foreign process. + */ + private final int attempts; + + /** + * The pause between two checks for another process to return. + */ + private final long pause; + + /** + * The time unit of the pause time. + */ + private final TimeUnit timeUnit; + + /** + * Creates a new connector for a Linux POSIX enviornment using stat. + * + * @param attempts The maximum amount of attempts for checking the result of a foreign process. + * @param pause The pause between two checks for another process to return. + * @param timeUnit The time unit of the pause time. + */ + public ForLinuxEnvironment(int attempts, long pause, TimeUnit timeUnit) { + this.attempts = attempts; + this.pause = pause; + this.timeUnit = timeUnit; + } + + /** + * Returns the user id of the owner of the supplied file. + * + * @param file The file for which to locate the owner. + * @return The owner id of the supplied file. + */ + @Override + public int getOwnerIdOf(File file) { + try { + // The binding for 'stat' is very platform dependant. To avoid the complexity of binding the correct method, + // stat is called as a separate command. This is less efficient but more portable. + Process process = Runtime.getRuntime().exec(new String[]{"stat", + "-f", + "%u", + file.getAbsolutePath()}); + int attempts = this.attempts; + boolean exited = false; + String line = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")).readLine(); + do { + try { + if (process.exitValue() != 0) { + throw new IllegalStateException("Error while executing stat"); + } + exited = true; + break; + } catch (IllegalThreadStateException ignored) { + try { + Thread.sleep(timeUnit.toMillis(pause)); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(exception); + } + } + } while (--attempts > 0); + if (!exited) { + process.destroy(); + throw new IllegalStateException("Command for stat did not exit in time"); + } + return Integer.parseInt(line); + } catch (IOException exception) { + throw new IllegalStateException("Unable to execute stat command", exception); + } + } + } + + class ForAixEnvironment implements PosixOwner { + + /** + * The maximum amount of attempts for checking the result of a foreign process. + */ + private final int attempts; + + /** + * The pause between two checks for another process to return. + */ + private final long pause; + + /** + * The time unit of the pause time. + */ + private final TimeUnit timeUnit; + + /** + * Creates a new connector for an AIX POSIX enviornment using stat. + * + * @param attempts The maximum amount of attempts for checking the result of a foreign process. + * @param pause The pause between two checks for another process to return. + * @param timeUnit The time unit of the pause time. + */ + public ForAixEnvironment(int attempts, long pause, TimeUnit timeUnit) { + this.attempts = attempts; + this.pause = pause; + this.timeUnit = timeUnit; + } + + private static final Pattern AIX_OWNER_PATTERN = Pattern.compile("Owner: (\\d+)\\("); + + /** + * Returns the user id of the owner of the supplied file. + * + * @param file The file for which to locate the owner. + * @return The owner id of the supplied file. + */ + @Override + public int getOwnerIdOf(File file) { + try { + Process process = Runtime.getRuntime().exec(new String[]{"istat", file.getAbsolutePath()}); + int attempts = this.attempts; + boolean exited = false; + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); + StringBuilder output = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + output.append(line).append("\n"); + } + do { + try { + if (process.exitValue() != 0) { + throw new IllegalStateException("Error while executing istat"); + } + exited = true; + break; + } catch (IllegalThreadStateException ignored) { + try { + Thread.sleep(timeUnit.toMillis(pause)); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(exception); + } + } + } while (--attempts > 0); + if (!exited) { + process.destroy(); + throw new IllegalStateException("Command for istat did not exit in time"); + } + Matcher matcher = AIX_OWNER_PATTERN.matcher(output.toString()); + // Find and print the Owner UID + if (matcher.find()) { + return Integer.parseInt(matcher.group(1)); + } else { + throw new IllegalStateException("Unable to parse response from istat command: " + output); + } + } catch (IOException exception) { + throw new IllegalStateException("Unable to execute istat command", exception); + } + } + } + + class ForMacEnvironment implements PosixOwner { + + /** + * The maximum amount of attempts for checking the result of a foreign process. + */ + private final int attempts; + + /** + * The pause between two checks for another process to return. + */ + private final long pause; + + /** + * The time unit of the pause time. + */ + private final TimeUnit timeUnit; + + /** + * Creates a new connector for a Mac POSIX enviornment using stat. + * + * @param attempts The maximum amount of attempts for checking the result of a foreign process. + * @param pause The pause between two checks for another process to return. + * @param timeUnit The time unit of the pause time. + */ + public ForMacEnvironment(int attempts, long pause, TimeUnit timeUnit) { + this.attempts = attempts; + this.pause = pause; + this.timeUnit = timeUnit; + } + + /** + * Returns the user id of the owner of the supplied file. + * + * @param file The file for which to locate the owner. + * @return The owner id of the supplied file. + */ + @Override + public int getOwnerIdOf(File file) { + try { + // The binding for 'stat' is very platform dependant. To avoid the complexity of binding the correct method, + // stat is called as a separate command. This is less efficient but more portable. + Process process = Runtime.getRuntime().exec(new String[]{"stat", + "-f", + "%u", + file.getAbsolutePath()}); + int attempts = this.attempts; + boolean exited = false; + String line = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")).readLine(); + do { + try { + if (process.exitValue() != 0) { + throw new IllegalStateException("Error while executing stat"); + } + exited = true; + break; + } catch (IllegalThreadStateException ignored) { + try { + Thread.sleep(timeUnit.toMillis(pause)); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(exception); + } + } + } while (--attempts > 0); + if (!exited) { + process.destroy(); + throw new IllegalStateException("Command for stat did not exit in time"); + } + return Integer.parseInt(line); + } catch (IOException exception) { + throw new IllegalStateException("Unable to execute stat command", exception); + } + } + } + } } /**