From 4dcae48a47d1c2123d5ec86f3e2d6ef1adab8a83 Mon Sep 17 00:00:00 2001 From: Marian Koncek Date: Wed, 30 Sep 2020 13:04:45 +0200 Subject: [PATCH] Remove dependency on jna --- .../net/bytebuddy/agent/VirtualMachine.java | 2275 +++-------------- 1 file changed, 357 insertions(+), 1918 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 245581d..80cab63 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 @@ -15,13 +15,6 @@ */ package net.bytebuddy.agent; -import com.sun.jna.*; -import com.sun.jna.platform.win32.*; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.win32.StdCallLibrary; -import com.sun.jna.win32.W32APIOptions; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import java.io.*; import java.net.ServerSocket; import java.net.Socket; @@ -227,23 +220,6 @@ public interface VirtualMachine { this.connection = connection; } - /** - * Attaches to the supplied process id using the default JNA implementation. - * - * @param processId The process id. - * @return A suitable virtual machine implementation. - * @throws IOException If an IO exception occurs during establishing the connection. - */ - public static VirtualMachine attach(String processId) throws IOException { - if (Platform.isWindows()) { - return attach(processId, new Connection.ForJnaWindowsNamedPipe.Factory()); - } else if (Platform.isSolaris()) { - return attach(processId, new Connection.ForJnaSolarisDoor.Factory(15, 100, TimeUnit.MILLISECONDS)); - } else { - return attach(processId, Connection.ForJnaPosixSocket.Factory.withDefaultTemporaryFolder(15, 100, TimeUnit.MILLISECONDS)); - } - } - /** * Attaches to the supplied process id using the supplied connection factory. * @@ -515,7 +491,6 @@ public interface VirtualMachine { /** * {@inheritDoc} */ - @SuppressFBWarnings(value = "DMI_HARDCODED_ABSOLUTE_FILENAME", justification = "File name convention is specified.") public Connection connect(String processId) throws IOException { File socket = new File(temporaryDirectory, SOCKET_FILE_PREFIX + processId); if (!socket.exists()) { @@ -679,1949 +654,413 @@ public interface VirtualMachine { */ protected abstract int read(T connection, byte[] buffer) throws IOException; } + } + } - /** - * Implements a connection for a Posix socket in JNA. - */ - class ForJnaPosixSocket extends OnPersistentByteChannel { + /** + * A virtual machine attachment implementation for OpenJ9 or any compatible JVM. + */ + class ForOpenJ9 extends AbstractBase { - /** - * The JNA library to use. - */ - private final PosixLibrary library; + /** + * The temporary folder for attachment files for OpenJ9 VMs. + */ + private static final String IBM_TEMPORARY_FOLDER = "com.ibm.tools.attach.directory"; - /** - * The POSIX socket. - */ - private final File socket; + /** + * The socket on which this VM and the target VM communicate. + */ + private final Socket socket; - /** - * Creates a connection for a virtual posix socket implemented in JNA. - * - * @param library The JNA library to use. - * @param socket The POSIX socket. - */ - protected ForJnaPosixSocket(PosixLibrary library, File socket) { - this.library = library; - this.socket = socket; - } + /** + * Creates a new virtual machine connection for OpenJ9. + * + * @param socket The socket on which this VM and the target VM communicate. + */ + protected ForOpenJ9(Socket socket) { + this.socket = socket; + } - @Override - protected Integer connect() { - int handle = library.socket(1, 1, 0); + /** + * Attaches to the supplied process id. + * + * @param processId The process id. + * @param timeout The timeout for establishing the socket connection. + * @param dispatcher The connector to use to communicate with the target VM. + * @return A suitable virtual machine implementation. + * @throws IOException If an IO exception occurs during establishing the connection. + */ + public static VirtualMachine attach(String processId, int timeout, Dispatcher dispatcher) throws IOException { + File directory = new File(System.getProperty(IBM_TEMPORARY_FOLDER, dispatcher.getTemporaryFolder()), ".com_ibm_tools_attach"); + RandomAccessFile attachLock = new RandomAccessFile(new File(directory, "_attachlock"), "rw"); + try { + FileLock attachLockLock = attachLock.getChannel().lock(); + try { + List virtualMachines; + RandomAccessFile master = new RandomAccessFile(new File(directory, "_master"), "rw"); try { - PosixLibrary.SocketAddress address = new PosixLibrary.SocketAddress(); + FileLock masterLock = master.getChannel().lock(); try { - address.setPath(socket.getAbsolutePath()); - library.connect(handle, address, address.size()); - return handle; - } finally { - address = null; - } - } catch (RuntimeException exception) { - library.close(handle); - throw exception; - } - } - - @Override - protected int read(Integer handle, byte[] buffer) { - int read = library.read(handle, ByteBuffer.wrap(buffer), buffer.length); - return read == 0 ? -1 : read; - } - - @Override - protected void write(Integer handle, byte[] buffer) { - library.write(handle, ByteBuffer.wrap(buffer), buffer.length); - } - - @Override - protected void close(Integer handle) { - library.close(handle); - } - - /** - * {@inheritDoc} - */ - public void close() { - /* do nothing */ - } - - /** - * A JNA library binding for Posix sockets. - */ - protected interface PosixLibrary extends Library { - - /** - * Sends a kill command. - * - * @param processId The process id to kill. - * @param signal The signal to send. - * @return The return code. - * @throws LastErrorException If an error occurs. - */ - int kill(int processId, int signal) throws LastErrorException; - - /** - * Creates a POSIX socket connection. - * - * @param domain The socket's domain. - * @param type The socket's type. - * @param protocol The protocol version. - * @return A handle to the socket that was created or {@code 0} if no socket could be created. - * @throws LastErrorException If an error occurs. - */ - int socket(int domain, int type, int protocol) throws LastErrorException; - - /** - * Connects a socket connection. - * - * @param handle The socket's handle. - * @param address The address of the POSIX socket. - * @param length The length of the socket value. - * @return The return code. - * @throws LastErrorException If an error occurs. - */ - int connect(int handle, SocketAddress address, int length) throws LastErrorException; - - /** - * Reads from a POSIX socket. - * - * @param handle The socket's handle. - * @param buffer The buffer to read from. - * @param count The bytes being read. - * @return The amount of bytes that could be read. - * @throws LastErrorException If an error occurs. - */ - int read(int handle, ByteBuffer buffer, int count) throws LastErrorException; - - /** - * Writes to a POSIX socket. - * - * @param handle The socket's handle. - * @param buffer The buffer to write to. - * @param count The bytes being written. - * @return The return code. - * @throws LastErrorException If an error occurs. - */ - int write(int handle, ByteBuffer buffer, int count) throws LastErrorException; - - /** - * Closes the socket connection. - * - * @param handle The handle of the connection. - * @return The return code. - * @throws LastErrorException If an error occurs. - */ - int close(int handle) throws LastErrorException; - - /** - * Represents an address for a POSIX socket. - */ - class SocketAddress extends Structure { - - /** - * The socket family. - */ - @SuppressWarnings("unused") - @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "Field required by native implementation.") - public short family = 1; - - /** - * The socket path. - */ - public byte[] path = new byte[100]; - - /** - * Sets the socket path. - * - * @param path The socket path. - */ - protected void setPath(String path) { - try { - System.arraycopy(path.getBytes("UTF-8"), 0, this.path, 0, path.length()); - System.arraycopy(new byte[]{0}, 0, this.path, path.length(), 1); - } catch (UnsupportedEncodingException exception) { - throw new IllegalStateException(exception); + File[] vmFolder = directory.listFiles(); + if (vmFolder == null) { + throw new IllegalStateException("No descriptor files found in " + directory); } - } - - @Override - protected List getFieldOrder() { - return Arrays.asList("family", "path"); - } - } - } - - /** - * A factory for a POSIX socket connection to a JVM using JNA. - */ - public static class Factory extends Connection.Factory.ForSocketFile { - - /** - * The socket library API. - */ - private final PosixLibrary library; - - /** - * Creates a connection factory for a POSIX socket using JNA. - * - * @param temporaryDirectory The temporary directory to use. - * @param attempts The maximum amount of attempts for checking the establishment of a socket connection. - * @param pause The pause between two checks for an established socket connection. - * @param timeUnit The time unit of the pause time. - */ - @SuppressWarnings("deprecation") - public Factory(String temporaryDirectory, int attempts, long pause, TimeUnit timeUnit) { - super(temporaryDirectory, attempts, pause, timeUnit); - library = Native.loadLibrary("c", PosixLibrary.class); - } - - /** - * Creates a connection factory for a POSIX socket using JNA while locating the default temporary directory used on the - * current platform. - * - * @param attempts The maximum amount of attempts for checking the establishment of a socket connection. - * @param pause The pause between two checks for an established socket connection. - * @param timeUnit The time unit of the pause time. - * @return An appropriate connection factory. - */ - @SuppressWarnings("deprecation") - public static Connection.Factory withDefaultTemporaryFolder(int attempts, long pause, TimeUnit timeUnit) { - String temporaryDirectory; - if (Platform.isMac()) { - MacLibrary library = Native.loadLibrary("c", MacLibrary.class); - Memory memory = new Memory(4096); - try { - long length = library.confstr(MacLibrary.CS_DARWIN_USER_TEMP_DIR, memory, memory.size()); - if (length == 0 || length > 4096) { - temporaryDirectory = "/tmp"; - } else { - temporaryDirectory = memory.getString(0); + long userId = dispatcher.userId(); + virtualMachines = new ArrayList(); + for (File aVmFolder : vmFolder) { + if (aVmFolder.isDirectory() && dispatcher.getOwnerIdOf(aVmFolder) == userId) { + File attachInfo = new File(aVmFolder, "attachInfo"); + if (attachInfo.isFile()) { + Properties virtualMachine = new Properties(); + FileInputStream inputStream = new FileInputStream(attachInfo); + try { + virtualMachine.load(inputStream); + } finally { + inputStream.close(); + } + int targetProcessId = Integer.parseInt(virtualMachine.getProperty("processId")); + long targetUserId; + try { + targetUserId = Long.parseLong(virtualMachine.getProperty("userUid")); + } catch (NumberFormatException ignored) { + targetUserId = 0L; + } + if (userId != 0L && targetUserId == 0L) { + targetUserId = dispatcher.getOwnerIdOf(attachInfo); + } + if (targetProcessId == 0L || dispatcher.isExistingProcess(targetProcessId)) { + virtualMachines.add(virtualMachine); + } else if (userId == 0L || targetUserId == userId) { + File[] vmFile = aVmFolder.listFiles(); + if (vmFile != null) { + for (File aVmFile : vmFile) { + if (!aVmFile.delete()) { + aVmFile.deleteOnExit(); + } + } + } + if (!aVmFolder.delete()) { + aVmFolder.deleteOnExit(); + } + } + } } - } finally { - memory = null; } - } else { - temporaryDirectory = "/tmp"; + } finally { + masterLock.release(); } - return new Factory(temporaryDirectory, attempts, pause, timeUnit); - } - - @Override - protected void kill(String processId, int signal) { - library.kill(Integer.parseInt(processId), signal); - } - - @Override - public Connection doConnect(File socket) { - return new Connection.ForJnaPosixSocket(library, socket); - } - - /** - * A library for reading a Mac user's temporary directory. - */ - public interface MacLibrary extends Library { - - /** - * The temporary directory. - */ - int CS_DARWIN_USER_TEMP_DIR = 65537; - - /** - * Reads a configuration dependant variable into a memory segment. - * - * @param name The name of the variable. - * @param buffer The buffer to read the variable into. - * @param length The length of the buffer. - * @return The amount of bytes written to the buffer. - */ - long confstr(int name, Pointer buffer, long length); + } finally { + master.close(); } - } - } - - /** - * Implements a connection for a Windows named pipe in JNA. - */ - class ForJnaWindowsNamedPipe implements Connection { - - /** - * Indicates a memory release. - */ - private static final int MEM_RELEASE = 0x8000; - - /** - * The library to use for communicating with Windows native functions. - */ - private final WindowsLibrary library; - - /** - * The library to use for communicating with Windows attachment extension that is included as a DLL. - */ - private final WindowsAttachLibrary attachLibrary; - - /** - * The handle of the target VM's process. - */ - private final WinNT.HANDLE process; - - /** - * A pointer to the code that was injected into the target process. - */ - private final WinDef.LPVOID code; - - /** - * A source of random values being used for generating pipe names. - */ - private final SecureRandom random; - - /** - * Creates a new connection via a named pipe. - * - * @param library The library to use for communicating with Windows native functions. - * @param attachLibrary The library to use for communicating with Windows attachment extension that is included as a DLL. - * @param process The handle of the target VM's process. - * @param code A pointer to the code that was injected into the target process. - */ - protected ForJnaWindowsNamedPipe(WindowsLibrary library, - WindowsAttachLibrary attachLibrary, - WinNT.HANDLE process, - WinDef.LPVOID code) { - this.library = library; - this.attachLibrary = attachLibrary; - this.process = process; - this.code = code; - random = new SecureRandom(); - } - - /** - * {@inheritDoc} - */ - public Response execute(String protocol, String... argument) { - if (!"1".equals(protocol)) { - throw new IllegalArgumentException("Unknown protocol version: " + protocol); - } else if (argument.length > 4) { - throw new IllegalArgumentException("Cannot supply more then four arguments to Windows attach mechanism: " + Arrays.asList(argument)); + Properties target = null; + for (Properties virtualMachine : virtualMachines) { + if (virtualMachine.getProperty("processId").equalsIgnoreCase(processId)) { + target = virtualMachine; + break; + } } - String name = "\\\\.\\pipe\\javatool" + Math.abs(random.nextInt() + 1); - WinNT.HANDLE pipe = Kernel32.INSTANCE.CreateNamedPipe(name, - WinBase.PIPE_ACCESS_INBOUND, - WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, - 1, - 4096, - 8192, - WinBase.NMPWAIT_USE_DEFAULT_WAIT, - null); - if (pipe == null) { - throw new Win32Exception(Native.getLastError()); + if (target == null) { + throw new IllegalStateException("Could not locate target process info in " + directory); } + ServerSocket serverSocket = new ServerSocket(0); try { - WinDef.LPVOID data = attachLibrary.allocate_remote_argument(process, - name, - argument.length < 1 ? null : argument[0], - argument.length < 2 ? null : argument[1], - argument.length < 3 ? null : argument[2], - argument.length < 4 ? null : argument[3]); - if (data == null) { - throw new Win32Exception(Native.getLastError()); - } + serverSocket.setSoTimeout(timeout); + File receiver = new File(directory, target.getProperty("vmId")); + String key = Long.toHexString(new SecureRandom().nextLong()); + File reply = new File(receiver, "replyInfo"); try { - WinNT.HANDLE thread = library.CreateRemoteThread(process, null, 0, code.getPointer(), data.getPointer(), null, null); - if (thread == null) { - throw new Win32Exception(Native.getLastError()); + if (reply.createNewFile()) { + dispatcher.setPermissions(reply, 0600); } + FileOutputStream outputStream = new FileOutputStream(reply); try { - int result = Kernel32.INSTANCE.WaitForSingleObject(thread, WinBase.INFINITE); - if (result != 0) { - throw new Win32Exception(result); - } - IntByReference exitCode = new IntByReference(); - if (!library.GetExitCodeThread(thread, exitCode)) { - throw new Win32Exception(Native.getLastError()); - } else if (exitCode.getValue() != 0) { - throw new IllegalStateException("Target could not dispatch command successfully"); - } - if (!Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) { - int code = Native.getLastError(); - if (code != WinError.ERROR_PIPE_CONNECTED) { - throw new Win32Exception(code); - } - } - return new NamedPipeResponse(pipe); + outputStream.write(key.getBytes("UTF-8")); + outputStream.write("\n".getBytes("UTF-8")); + outputStream.write(Long.toString(serverSocket.getLocalPort()).getBytes("UTF-8")); + outputStream.write("\n".getBytes("UTF-8")); } finally { - if (!Kernel32.INSTANCE.CloseHandle(thread)) { - throw new Win32Exception(Native.getLastError()); - } - } - } finally { - if (!library.VirtualFreeEx(process, data.getPointer(), 0, MEM_RELEASE)) { - throw new Win32Exception(Native.getLastError()); + outputStream.close(); } - } - } catch (Throwable throwable) { - if (!Kernel32.INSTANCE.CloseHandle(pipe)) { - throw new Win32Exception(Native.getLastError()); - } else if (throwable instanceof RuntimeException) { - throw (RuntimeException) throwable; - } else { - throw new IllegalStateException(throwable); - } - } - } - - /** - * {@inheritDoc} - */ - public void close() { - try { - if (!library.VirtualFreeEx(process, code.getPointer(), 0, MEM_RELEASE)) { - throw new Win32Exception(Native.getLastError()); - } - } finally { - if (!Kernel32.INSTANCE.CloseHandle(process)) { - throw new Win32Exception(Native.getLastError()); - } - } - } - - /** - * A library for interacting with Windows. - */ - protected interface WindowsLibrary extends StdCallLibrary { - - /** - * Changes the state of memory in a given process. - * - * @param process The process in which to change the memory. - * @param address The address of the memory to allocate. - * @param size The size of the allocated region. - * @param allocationType The allocation type. - * @param protect The memory protection. - * @return A pointer to the allocated memory. - */ - @SuppressWarnings({"unused", "checkstyle:methodname"}) - Pointer VirtualAllocEx(WinNT.HANDLE process, Pointer address, int size, int allocationType, int protect); - - /** - * Frees memory in the given process. - * - * @param process The process in which to change the memory. - * @param address The address of the memory to free. - * @param size The size of the freed region. - * @param freeType The freeing type. - * @return {@code true} if the operation succeeded. - */ - @SuppressWarnings("checkstyle:methodname") - boolean VirtualFreeEx(WinNT.HANDLE process, Pointer address, int size, int freeType); - - /** - * An alternative implementation of - * {@link Kernel32#CreateRemoteThread(WinNT.HANDLE, WinBase.SECURITY_ATTRIBUTES, int, WinBase.FOREIGN_THREAD_START_ROUTINE, Pointer, WinDef.DWORD, Pointer)} - * that uses a pointer as the {@code code} argument rather then a structure to avoid accessing foreign memory. - * - * @param process A handle of the target process. - * @param securityAttributes The security attributes to use or {@code null} if no attributes are provided. - * @param stackSize The stack size or {@code 0} for using the system default. - * @param code A pointer to the code to execute. - * @param argument A pointer to the argument to provide to the code being executed. - * @param creationFlags The creation flags or {@code null} if no flags are set. - * @param threadId A pointer to the thread id or {@code null} if no thread reference is set. - * @return A handle to the created remote thread or {@code null} if the creation failed. - */ - @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE CreateRemoteThread(WinNT.HANDLE process, - WinBase.SECURITY_ATTRIBUTES securityAttributes, - int stackSize, - Pointer code, - Pointer argument, - WinDef.DWORD creationFlags, - Pointer threadId); - - /** - * Receives the exit code of a given thread. - * - * @param thread A handle to the targeted thread. - * @param exitCode A reference to the exit code value. - * @return {@code true} if the exit code retrieval succeeded. - */ - @SuppressWarnings("checkstyle:methodname") - boolean GetExitCodeThread(WinNT.HANDLE thread, IntByReference exitCode); - } - - /** - * A library for interacting with Windows. - */ - protected interface WindowsAttachLibrary extends StdCallLibrary { - - /** - * Allocates the code to invoke on the remote VM. - * - * @param process A handle to the target process. - * @return A pointer to the allocated code or {@code null} if the code could not be allocated. - */ - @SuppressWarnings("checkstyle:methodname") - WinDef.LPVOID allocate_remote_code(WinNT.HANDLE process); - - /** - * Allocates the remote argument to supply to the remote code upon execution. - * - * @param process A handle to the target process. - * @param pipe The name of the pipe used for supplying an answer. - * @param argument0 The first argument or {@code null} if no such argument is provided. - * @param argument1 The second argument or {@code null} if no such argument is provided. - * @param argument2 The third argument or {@code null} if no such argument is provided. - * @param argument3 The forth argument or {@code null} if no such argument is provided. - * @return A pointer to the allocated argument or {@code null} if the argument could not be allocated. - */ - @SuppressWarnings("checkstyle:methodname") - WinDef.LPVOID allocate_remote_argument(WinNT.HANDLE process, - String pipe, - String argument0, - String argument1, - String argument2, - String argument3); - } - - /** - * A response that is sent via a named pipe. - */ - protected static class NamedPipeResponse implements Response { - - /** - * A handle of the named pipe. - */ - private final WinNT.HANDLE pipe; - - /** - * Creates a new response via a named pipe. - * - * @param pipe The handle of the named pipe. - */ - protected NamedPipeResponse(WinNT.HANDLE pipe) { - this.pipe = pipe; - } - - /** - * {@inheritDoc} - */ - public int read(byte[] buffer) { - IntByReference read = new IntByReference(); - if (!Kernel32.INSTANCE.ReadFile(pipe, buffer, buffer.length, read, null)) { - int code = Native.getLastError(); - if (code == WinError.ERROR_BROKEN_PIPE) { - return -1; - } else { - throw new Win32Exception(code); - } - } - return read.getValue(); - } - - /** - * {@inheritDoc} - */ - public void close() { - try { - if (!Kernel32.INSTANCE.DisconnectNamedPipe(pipe)) { - throw new Win32Exception(Native.getLastError()); - } - } finally { - if (!Kernel32.INSTANCE.CloseHandle(pipe)) { - throw new Win32Exception(Native.getLastError()); - } - } - } - } - - /** - * A factory for establishing a connection to a JVM using a named pipe in JNA. - */ - public static class Factory implements Connection.Factory { - - /** - * The name of the native code library that is included in this artifact to support Windows attachment. - * This property can be set by other libraries that shade Byte Buddy agent and relocates the library. - */ - public static final String LIBRARY_NAME = "net.bytebuddy.library.name"; - - /** - * The library to use for communicating with Windows native functions. - */ - private final WindowsLibrary library; - - /** - * The library to use for communicating with Windows attachment extension that is included as a DLL. - */ - private final WindowsAttachLibrary attachLibrary; - - /** - * Creates a new connection factory for Windows using JNA. - */ - @SuppressWarnings("deprecation") - public Factory() { - library = Native.loadLibrary("kernel32", WindowsLibrary.class, W32APIOptions.DEFAULT_OPTIONS); - attachLibrary = Native.loadLibrary(System.getProperty(LIBRARY_NAME, "attach_hotspot_windows"), WindowsAttachLibrary.class); - } - - /** - * {@inheritDoc} - */ - public Connection connect(String processId) { - WinNT.HANDLE process = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_ALL_ACCESS, false, Integer.parseInt(processId)); - if (process == null) { - throw new Win32Exception(Native.getLastError()); - } - try { - WinDef.LPVOID code = attachLibrary.allocate_remote_code(process); - if (code == null) { - throw new Win32Exception(Native.getLastError()); - } - return new ForJnaWindowsNamedPipe(library, attachLibrary, process, code); - } catch (Throwable throwable) { - if (!Kernel32.INSTANCE.CloseHandle(process)) { - throw new Win32Exception(Native.getLastError()); - } else if (throwable instanceof RuntimeException) { - throw (RuntimeException) throwable; - } else { - throw new IllegalStateException(throwable); - } - } - } - } - } - - /** - * A connection to a VM using a Solaris door. - */ - class ForJnaSolarisDoor implements Connection { - - /** - * The library to use for interacting with Solaris. - */ - private final SolarisLibrary library; - - /** - * The socket used for communication. - */ - private final File socket; - - /** - * Creates a new connection using a Solaris door. - * - * @param library The library to use for interacting with Solaris. - * @param socket The socket used for communication. - */ - protected ForJnaSolarisDoor(SolarisLibrary library, File socket) { - this.library = library; - this.socket = socket; - } - - /** - * {@inheritDoc} - */ - @SuppressFBWarnings(value = {"UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD", "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"}, justification = "This pattern is required for use of JNA.") - public Connection.Response execute(String protocol, String... argument) throws IOException { - int handle = library.open(socket.getAbsolutePath(), 2); - try { - SolarisLibrary.DoorArgument door = new SolarisLibrary.DoorArgument(); - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(protocol.getBytes("UTF-8")); - outputStream.write(0); - for (String anArgument : argument) { - if (anArgument != null) { - outputStream.write(anArgument.getBytes("UTF-8")); - } - outputStream.write(0); - } - door.dataSize = outputStream.size(); - Memory dataPointer = new Memory(outputStream.size()); - try { - dataPointer.write(0, outputStream.toByteArray(), 0, outputStream.size()); - door.dataPointer = dataPointer; - Memory result = new Memory(128); - try { - door.resultPointer = result; - door.resultSize = (int) result.size(); - if (library.door_call(handle, door) != 0) { - throw new IllegalStateException("Door call to target VM failed"); - } else if (door.resultSize < 4 || door.resultPointer.getInt(0) != 0) { - throw new IllegalStateException("Target VM could not execute door call"); - } else if (door.descriptorCount != 1 || door.descriptorPointer == null) { - throw new IllegalStateException("Did not receive communication descriptor from target VM"); - } else { - return new Response(library, door.descriptorPointer.getInt(4)); - } - } finally { - result = null; - } - } finally { - dataPointer = null; - } - } finally { - door = null; - } - } finally { - library.close(handle); - } - } - - /** - * {@inheritDoc} - */ - public void close() { - /* do nothing */ - } - - /** - * A library for interacting with Solaris. - */ - protected interface SolarisLibrary extends Library { - - /** - * Sends a kill signal to the target VM. - * - * @param processId The target process's id. - * @param signal The signal to send. - * @return The return code. - * @throws LastErrorException If an error occurred while sending the signal. - */ - int kill(int processId, int signal) throws LastErrorException; - - /** - * Opens a file. - * - * @param file The file name. - * @param flags the flags for opening. - * @return The file descriptor. - * @throws LastErrorException If the file could not be opened. - */ - int open(String file, int flags) throws LastErrorException; - - /** - * Reads from a handle. - * - * @param handle The handle representing the source being read. - * @param buffer The buffer to read to. - * @param length The buffer length. - * @return The amount of bytes being read. - * @throws LastErrorException If a read operation failed. - */ - int read(int handle, ByteBuffer buffer, int length) throws LastErrorException; - - /** - * Releases a descriptor. - * - * @param descriptor The descriptor to release. - * @return The return code. - * @throws LastErrorException If the descriptor could not be closed. - */ - int close(int descriptor) throws LastErrorException; - - /** - * Executes a door call. - * - * @param descriptor The door's descriptor. - * @param argument A pointer to the argument. - * @return The door's handle. - * @throws LastErrorException If the door call failed. - */ - @SuppressWarnings("checkstyle:methodname") - int door_call(int descriptor, DoorArgument argument) throws LastErrorException; - - /** - * A structure representing the argument to a Solaris door operation. - */ - class DoorArgument extends Structure { - - /** - * A pointer to the operation argument. - */ - public Pointer dataPointer; - - /** - * The size of the argument being pointed to. - */ - public int dataSize; - - /** - * A pointer to the operation descriptor. - */ - public Pointer descriptorPointer; - - /** - * The size of the operation argument. - */ - public int descriptorCount; - - /** - * A pointer to the operation result. - */ - public Pointer resultPointer; - - /** - * The size of the operation argument. - */ - public int resultSize; - - @Override - protected List getFieldOrder() { - return Arrays.asList("dataPointer", "dataSize", "descriptorPointer", "descriptorCount", "resultPointer", "resultSize"); - } - } - } - - /** - * A response from a VM using a Solaris door. - */ - protected static class Response implements Connection.Response { - - /** - * The Solaris library to use. - */ - private final SolarisLibrary library; - - /** - * The door handle. - */ - private final int handle; - - /** - * Creates a response from a VM using a Solaris door. - * - * @param library The Solaris library to use. - * @param handle The door handle. - */ - protected Response(SolarisLibrary library, int handle) { - this.library = library; - this.handle = handle; - } - - /** - * {@inheritDoc} - */ - public int read(byte[] buffer) { - int read = library.read(handle, ByteBuffer.wrap(buffer), buffer.length); - return read == 0 ? -1 : read; - } - - /** - * {@inheritDoc} - */ - public void close() { - library.close(handle); - } - } - - /** - * A factory for establishing a connection to a JVM using a Solaris door in JNA. - */ - public static class Factory extends Connection.Factory.ForSocketFile { - - /** - * The library to use for interacting with Solaris. - */ - private final SolarisLibrary library; - - /** - * Creates a new connection factory for a Solaris VM. - * - * @param attempts The maximum amount of attempts for checking the establishment of a socket connection. - * @param pause The pause between two checks for an established socket connection. - * @param timeUnit The time unit of the pause time. - */ - @SuppressWarnings("deprecation") - public Factory(int attempts, long pause, TimeUnit timeUnit) { - super("/tmp", attempts, pause, timeUnit); - library = Native.loadLibrary("c", SolarisLibrary.class); - } - - /** - * {@inheritDoc} - */ - protected void kill(String processId, int signal) { - library.kill(Integer.parseInt(processId), signal); - } - - /** - * {@inheritDoc} - */ - protected Connection doConnect(File socket) { - return new ForJnaSolarisDoor(library, socket); - } - } - } - } - } - - /** - * A virtual machine attachment implementation for OpenJ9 or any compatible JVM. - */ - class ForOpenJ9 extends AbstractBase { - - /** - * The temporary folder for attachment files for OpenJ9 VMs. - */ - private static final String IBM_TEMPORARY_FOLDER = "com.ibm.tools.attach.directory"; - - /** - * The socket on which this VM and the target VM communicate. - */ - private final Socket socket; - - /** - * Creates a new virtual machine connection for OpenJ9. - * - * @param socket The socket on which this VM and the target VM communicate. - */ - protected ForOpenJ9(Socket socket) { - this.socket = socket; - } - - /** - * Attaches to the supplied process id using the default JNA implementation. - * - * @param processId The process id. - * @return A suitable virtual machine implementation. - * @throws IOException If an IO exception occurs during establishing the connection. - */ - public static VirtualMachine attach(String processId) throws IOException { - return attach(processId, 5000, Platform.isWindows() - ? new Dispatcher.ForJnaWindowsEnvironment() - : new Dispatcher.ForJnaPosixEnvironment(15, 100, TimeUnit.MILLISECONDS)); - } - - /** - * Attaches to the supplied process id. - * - * @param processId The process id. - * @param timeout The timeout for establishing the socket connection. - * @param dispatcher The connector to use to communicate with the target VM. - * @return A suitable virtual machine implementation. - * @throws IOException If an IO exception occurs during establishing the connection. - */ - public static VirtualMachine attach(String processId, int timeout, Dispatcher dispatcher) throws IOException { - File directory = new File(System.getProperty(IBM_TEMPORARY_FOLDER, dispatcher.getTemporaryFolder()), ".com_ibm_tools_attach"); - RandomAccessFile attachLock = new RandomAccessFile(new File(directory, "_attachlock"), "rw"); - try { - FileLock attachLockLock = attachLock.getChannel().lock(); - try { - List virtualMachines; - RandomAccessFile master = new RandomAccessFile(new File(directory, "_master"), "rw"); - try { - FileLock masterLock = master.getChannel().lock(); - try { - File[] vmFolder = directory.listFiles(); - if (vmFolder == null) { - throw new IllegalStateException("No descriptor files found in " + directory); - } - long userId = dispatcher.userId(); - virtualMachines = new ArrayList(); - for (File aVmFolder : vmFolder) { - if (aVmFolder.isDirectory() && dispatcher.getOwnerIdOf(aVmFolder) == userId) { - File attachInfo = new File(aVmFolder, "attachInfo"); - if (attachInfo.isFile()) { - Properties virtualMachine = new Properties(); - FileInputStream inputStream = new FileInputStream(attachInfo); - try { - virtualMachine.load(inputStream); - } finally { - inputStream.close(); - } - int targetProcessId = Integer.parseInt(virtualMachine.getProperty("processId")); - long targetUserId; - try { - targetUserId = Long.parseLong(virtualMachine.getProperty("userUid")); - } catch (NumberFormatException ignored) { - targetUserId = 0L; - } - if (userId != 0L && targetUserId == 0L) { - targetUserId = dispatcher.getOwnerIdOf(attachInfo); - } - if (targetProcessId == 0L || dispatcher.isExistingProcess(targetProcessId)) { - virtualMachines.add(virtualMachine); - } else if (userId == 0L || targetUserId == userId) { - File[] vmFile = aVmFolder.listFiles(); - if (vmFile != null) { - for (File aVmFile : vmFile) { - if (!aVmFile.delete()) { - aVmFile.deleteOnExit(); - } - } - } - if (!aVmFolder.delete()) { - aVmFolder.deleteOnExit(); - } - } - } - } - } - } finally { - masterLock.release(); - } - } finally { - master.close(); - } - Properties target = null; - for (Properties virtualMachine : virtualMachines) { - if (virtualMachine.getProperty("processId").equalsIgnoreCase(processId)) { - target = virtualMachine; - break; - } - } - if (target == null) { - throw new IllegalStateException("Could not locate target process info in " + directory); - } - ServerSocket serverSocket = new ServerSocket(0); - try { - serverSocket.setSoTimeout(timeout); - File receiver = new File(directory, target.getProperty("vmId")); - String key = Long.toHexString(new SecureRandom().nextLong()); - File reply = new File(receiver, "replyInfo"); - try { - if (reply.createNewFile()) { - dispatcher.setPermissions(reply, 0600); - } - FileOutputStream outputStream = new FileOutputStream(reply); - try { - outputStream.write(key.getBytes("UTF-8")); - outputStream.write("\n".getBytes("UTF-8")); - outputStream.write(Long.toString(serverSocket.getLocalPort()).getBytes("UTF-8")); - outputStream.write("\n".getBytes("UTF-8")); - } finally { - outputStream.close(); - } - Map locks = new HashMap(); - try { - String pid = Long.toString(dispatcher.pid()); - for (Properties virtualMachine : virtualMachines) { - if (!virtualMachine.getProperty("processId").equalsIgnoreCase(pid)) { - String attachNotificationSync = virtualMachine.getProperty("attachNotificationSync"); - RandomAccessFile syncFile = new RandomAccessFile(attachNotificationSync == null - ? new File(directory, "attachNotificationSync") - : new File(attachNotificationSync), "rw"); - try { - locks.put(syncFile, syncFile.getChannel().lock()); - } catch (IOException ignored) { - syncFile.close(); - } - } - } - int notifications = 0; - File[] item = directory.listFiles(); - if (item != null) { - for (File anItem : item) { - String name = anItem.getName(); - if (!name.startsWith(".trash_") - && !name.equalsIgnoreCase("_attachlock") - && !name.equalsIgnoreCase("_master") - && !name.equalsIgnoreCase("_notifier")) { - notifications += 1; - } - } - } - boolean global = Boolean.parseBoolean(target.getProperty("globalSemaphore")); - dispatcher.incrementSemaphore(directory, "_notifier", global, notifications); - try { - Socket socket = serverSocket.accept(); - String answer = new String(read(socket), "UTF-8"); - if (answer.contains(' ' + key + ' ')) { - return new ForOpenJ9(socket); - } else { - socket.close(); - throw new IllegalStateException("Unexpected answered to attachment: " + answer); - } - } finally { - dispatcher.decrementSemaphore(directory, "_notifier", global, notifications); - } - } finally { - for (Map.Entry entry : locks.entrySet()) { - try { - try { - entry.getValue().release(); - } finally { - entry.getKey().close(); - } - } catch (Throwable ignored) { - /* do nothing */ - } - } - } - } finally { - if (!reply.delete()) { - reply.deleteOnExit(); - } - } - } finally { - serverSocket.close(); - } - } finally { - attachLockLock.release(); - } - } finally { - attachLock.close(); - } - } - - /** - * {@inheritDoc} - */ - public Properties getSystemProperties() throws IOException { - write(socket, "ATTACH_GETSYSTEMPROPERTIES".getBytes("UTF-8")); - Properties properties = new Properties(); - properties.load(new ByteArrayInputStream(read(socket))); - return properties; - } - - /** - * {@inheritDoc} - */ - public Properties getAgentProperties() throws IOException { - write(socket, "ATTACH_GETAGENTPROPERTIES".getBytes("UTF-8")); - Properties properties = new Properties(); - properties.load(new ByteArrayInputStream(read(socket))); - return properties; - } - - /** - * {@inheritDoc} - */ - public void loadAgent(String jarFile, String argument) throws IOException { - write(socket, ("ATTACH_LOADAGENT(instrument," + jarFile + '=' + (argument == null ? "" : argument) + ')').getBytes("UTF-8")); - String answer = new String(read(socket), "UTF-8"); - if (answer.startsWith("ATTACH_ERR")) { - throw new IllegalStateException("Target VM failed loading agent: " + answer); - } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { - throw new IllegalStateException("Unexpected response: " + answer); - } - } - - /** - * {@inheritDoc} - */ - public void loadAgentPath(String path, String argument) throws IOException { - write(socket, ("ATTACH_LOADAGENTPATH(" + path + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); - String answer = new String(read(socket), "UTF-8"); - if (answer.startsWith("ATTACH_ERR")) { - throw new IllegalStateException("Target VM failed loading native agent: " + answer); - } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { - throw new IllegalStateException("Unexpected response: " + answer); - } - } - - /** - * {@inheritDoc} - */ - public void loadAgentLibrary(String library, String argument) throws IOException { - write(socket, ("ATTACH_LOADAGENTLIBRARY(" + library + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); - String answer = new String(read(socket), "UTF-8"); - if (answer.startsWith("ATTACH_ERR")) { - throw new IllegalStateException("Target VM failed loading native library: " + answer); - } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { - throw new IllegalStateException("Unexpected response: " + answer); - } - } - - /** - * {@inheritDoc} - */ - public void startManagementAgent(Properties properties) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - properties.store(outputStream, null); - write(socket, "ATTACH_START_MANAGEMENT_AGENT".getBytes("UTF-8")); - write(socket, outputStream.toByteArray()); - String answer = new String(read(socket), "UTF-8"); - if (answer.startsWith("ATTACH_ERR")) { - throw new IllegalStateException("Target VM could not start management agent: " + answer); - } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { - throw new IllegalStateException("Unexpected response: " + answer); - } - } - - /** - * {@inheritDoc} - */ - public String startLocalManagementAgent() throws IOException { - write(socket, "ATTACH_START_LOCAL_MANAGEMENT_AGENT".getBytes("UTF-8")); - String answer = new String(read(socket), "UTF-8"); - if (answer.startsWith("ATTACH_ERR")) { - throw new IllegalStateException("Target VM could not start management agent: " + answer); - } else if (answer.startsWith("ATTACH_ACK")) { - return answer.substring("ATTACH_ACK".length()); - } else if (answer.startsWith("ATTACH_RESULT=")) { - return answer.substring("ATTACH_RESULT=".length()); - } else { - throw new IllegalStateException("Unexpected response: " + answer); - } - } - - /** - * {@inheritDoc} - */ - public void detach() throws IOException { - try { - write(socket, "ATTACH_DETACH".getBytes("UTF-8")); - read(socket); // The answer is intentionally ignored. - } finally { - socket.close(); - } - } - - /** - * Writes the supplied value to the target socket. - * - * @param socket The socket to write to. - * @param value The value being written. - * @throws IOException If an I/O exception occurs. - */ - private static void write(Socket socket, byte[] value) throws IOException { - socket.getOutputStream().write(value); - socket.getOutputStream().write(0); - socket.getOutputStream().flush(); - } - - /** - * Reads a {@code '\0'}-terminated value from the target socket. - * - * @param socket The socket to read from. - * @return The value that was read. - * @throws IOException If an I/O exception occurs. - */ - private static byte[] read(Socket socket) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int length; - while ((length = socket.getInputStream().read(buffer)) != -1) { - if (length > 0 && buffer[length - 1] == 0) { - outputStream.write(buffer, 0, length - 1); - break; - } else { - outputStream.write(buffer, 0, length); - } - } - return outputStream.toByteArray(); - } - - /** - * A dispatcher for native operations being used for communication with an OpenJ9 virtual machine. - */ - public interface Dispatcher { - - /** - * Returns this machine's temporary folder. - * - * @return The temporary folder. - */ - String getTemporaryFolder(); - - /** - * Returns the process id of this process. - * - * @return The process id of this process. - */ - int pid(); - - /** - * Returns the user id of this process. - * - * @return The user id of this process - */ - int userId(); - - /** - * Returns {@code true} if the supplied process id is a running process. - * - * @param processId The process id to evaluate. - * @return {@code true} if the supplied process id is currently running. - */ - boolean isExistingProcess(int processId); - - /** - * 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); - - /** - * Sets permissions for the supplied file. - * - * @param file The file for which to set the permissions. - * @param permissions The permission bits to set. - */ - void setPermissions(File file, int permissions); - - /** - * Increments a semaphore. - * - * @param directory The sempahore's control directory. - * @param name The semaphore's name. - * @param global {@code true} if the semaphore is in the global namespace (only applicable on Windows). - * @param count The amount of increments. - */ - void incrementSemaphore(File directory, String name, boolean global, int count); - - /** - * Decrements a semaphore. - * - * @param directory The sempahore's control directory. - * @param name The semaphore's name. - * @param global {@code true} if the semaphore is in the global namespace (only applicable on Windows). - * @param count The amount of decrements. - */ - void decrementSemaphore(File directory, String name, boolean global, int count); - - /** - * A connector implementation for a POSIX environment using JNA. - */ - class ForJnaPosixEnvironment implements Dispatcher { - - /** - * The JNA library to use. - */ - 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; - - /** - * Creates a new connector for a POSIX enviornment using JNA. - * - * @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. - */ - @SuppressWarnings("deprecation") - public ForJnaPosixEnvironment(int attempts, long pause, TimeUnit timeUnit) { - this.attempts = attempts; - this.pause = pause; - this.timeUnit = timeUnit; - library = Native.loadLibrary("c", PosixLibrary.class); - } - - /** - * {@inheritDoc} - */ - public String getTemporaryFolder() { - String temporaryFolder = System.getenv("TMPDIR"); - return temporaryFolder == null ? "/tmp" : temporaryFolder; - } - - /** - * {@inheritDoc} - */ - public int pid() { - return library.getpid(); - } - - /** - * {@inheritDoc} - */ - public int userId() { - return library.getuid(); - } - - /** - * {@inheritDoc} - */ - public boolean isExistingProcess(int processId) { - return library.kill(processId, PosixLibrary.NULL_SIGNAL) != PosixLibrary.ESRCH; - } - - /** - * {@inheritDoc} - */ - @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. - String statUserSwitch = Platform.isMac() ? "-f" : "-c"; - Process process = Runtime.getRuntime().exec("stat " + statUserSwitch + " %u " + file.getAbsolutePath()); - int attempts = this.attempts; - boolean exited = false; - String line = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")).readLine(); - do { + Map locks = new HashMap(); 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); + String pid = Long.toString(dispatcher.pid()); + for (Properties virtualMachine : virtualMachines) { + if (!virtualMachine.getProperty("processId").equalsIgnoreCase(pid)) { + String attachNotificationSync = virtualMachine.getProperty("attachNotificationSync"); + RandomAccessFile syncFile = new RandomAccessFile(attachNotificationSync == null + ? new File(directory, "attachNotificationSync") + : new File(attachNotificationSync), "rw"); + try { + locks.put(syncFile, syncFile.getChannel().lock()); + } catch (IOException ignored) { + syncFile.close(); + } + } } - } - } 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); - } - } - - /** - * {@inheritDoc} - */ - public void setPermissions(File file, int permissions) { - library.chmod(file.getAbsolutePath(), permissions); - } - - /** - * {@inheritDoc} - */ - public void incrementSemaphore(File directory, String name, boolean global, int count) { - notifySemaphore(directory, name, count, (short) 1, (short) 0, false); - } - - /** - * {@inheritDoc} - */ - public void decrementSemaphore(File directory, String name, boolean global, int count) { - notifySemaphore(directory, name, count, (short) -1, (short) (PosixLibrary.SEM_UNDO | PosixLibrary.IPC_NOWAIT), true); - } - - /** - * Notifies a POSIX semaphore. - * - * @param directory The semaphore's directory. - * @param name The semaphore's name. - * @param count The amount of notifications to send. - * @param operation The operation to apply. - * @param flags The flags to set. - * @param acceptUnavailable {@code true} if a {@code EAGAIN} code should be accepted. - */ - @SuppressFBWarnings(value = {"URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"}, justification = "Modifier is required by JNA.") - private void notifySemaphore(File directory, String name, int count, short operation, short flags, boolean acceptUnavailable) { - int semaphore = library.semget(library.ftok(new File(directory, name).getAbsolutePath(), 0xA1), 2, 0666); - PosixLibrary.SemaphoreOperation target = new PosixLibrary.SemaphoreOperation(); - target.operation = operation; - target.flags = flags; - try { - while (count-- > 0) { - try { - library.semop(semaphore, target, 1); - } catch (LastErrorException exception) { - if (acceptUnavailable && (Native.getLastError() == PosixLibrary.EAGAIN - || Native.getLastError() == PosixLibrary.EDEADLK)) { - break; - } else { - throw exception; + int notifications = 0; + File[] item = directory.listFiles(); + if (item != null) { + for (File anItem : item) { + String name = anItem.getName(); + if (!name.startsWith(".trash_") + && !name.equalsIgnoreCase("_attachlock") + && !name.equalsIgnoreCase("_master") + && !name.equalsIgnoreCase("_notifier")) { + notifications += 1; + } + } } - } - } - } finally { - target = null; - } - } - - /** - * An API for interaction with POSIX systems. - */ - protected interface PosixLibrary extends Library { - - /** - * A null signal. - */ - int NULL_SIGNAL = 0; - - /** - * Indicates that a process does not exist. - */ - int ESRCH = 3; - - /** - * Indicates that a request timed out. - */ - int EAGAIN = 11; - - /** - * Indicates a dead lock on a resource. - */ - int EDEADLK = 35; - - /** - * Indicates that a semaphore's operations should be undone at process shutdown. - */ - short SEM_UNDO = 0x1000; - - /** - * Indicates that one should not wait for the release of a semaphore if it is not currently available. - */ - short IPC_NOWAIT = 04000; - - /** - * Runs the {@code getpid} command. - * - * @return The command's return value. - * @throws LastErrorException If an error occurred. - */ - int getpid() throws LastErrorException; - - /** - * Runs the {@code getuid} command. - * - * @return The command's return value. - * @throws LastErrorException If an error occurred. - */ - int getuid() throws LastErrorException; - - /** - * Runs the {@code kill} command. - * - * @param processId The target process id. - * @param signal The signal to send. - * @return The command's return value. - * @throws LastErrorException If an error occurred. - */ - int kill(int processId, int signal) throws LastErrorException; - - /** - * Runs the {@code chmod} command. - * - * @param path The file path. - * @param mode The mode to set. - * @return The return code. - * @throws LastErrorException If an error occurred. - */ - int chmod(String path, int mode) throws LastErrorException; - - /** - * Runs the {@code ftok} command. - * - * @param path The file path. - * @param id The id being used for creating the generated key. - * @return The generated key. - * @throws LastErrorException If an error occurred. - */ - int ftok(String path, int id) throws LastErrorException; - - /** - * Runs the {@code semget} command. - * - * @param key The key of the semaphore. - * @param count The initial count of the semaphore. - * @param flags The flags to set. - * @return The id of the semaphore. - * @throws LastErrorException If an error occurred. - */ - int semget(int key, int count, int flags) throws LastErrorException; - - /** - * Runs the {@code semop} command. - * - * @param id The id of the semaphore. - * @param operation The initial count of the semaphore. - * @param flags The flags to set. - * @return The return code. - * @throws LastErrorException If the operation was not successful. - */ - int semop(int id, SemaphoreOperation operation, int flags) throws LastErrorException; - - /** - * A structure to represent a semaphore operation for {@code semop}. - */ - class SemaphoreOperation extends Structure { - - /** - * The semaphore number. - */ - @SuppressWarnings("unused") - public short number; - - /** - * The operation to execute. - */ - public short operation; - - /** - * The flags being set for the operation. - */ - public short flags; - - @Override - protected List getFieldOrder() { - return Arrays.asList("number", "operation", "flags"); - } - } - } - } - - /** - * A connector implementation for a Windows environment using JNA. - */ - class ForJnaWindowsEnvironment implements Dispatcher { - - /** - * Indicates a missing user id what is not supported on Windows. - */ - private static final int NO_USER_ID = 0; - - /** - * The name of the creation mutex. - */ - private static final String CREATION_MUTEX_NAME = "j9shsemcreationMutex"; - - /** - * A library to use for interacting with Windows. - */ - private final WindowsLibrary library; - - /** - * Creates a new connector for a Windows environment using JNA. - */ - @SuppressWarnings("deprecation") - public ForJnaWindowsEnvironment() { - library = Native.loadLibrary("kernel32", WindowsLibrary.class, W32APIOptions.DEFAULT_OPTIONS); - } - - /** - * {@inheritDoc} - */ - public String getTemporaryFolder() { - WinDef.DWORD length = new WinDef.DWORD(WinDef.MAX_PATH); - char[] path = new char[length.intValue()]; - if (Kernel32.INSTANCE.GetTempPath(length, path).intValue() == 0) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - return Native.toString(path); - } - - /** - * {@inheritDoc} - */ - public int pid() { - return Kernel32.INSTANCE.GetCurrentProcessId(); - } - - /** - * {@inheritDoc} - */ - public int userId() { - return NO_USER_ID; - } - - /** - * {@inheritDoc} - */ - public boolean isExistingProcess(int processId) { - WinNT.HANDLE handle = Kernel32.INSTANCE.OpenProcess(WinNT.PROCESS_QUERY_INFORMATION, false, processId); - if (handle == null) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - IntByReference exists = new IntByReference(); - if (!Kernel32.INSTANCE.GetExitCodeProcess(handle, exists)) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - return exists.getValue() == WinBase.STILL_ACTIVE; - } - - /** - * {@inheritDoc} - */ - public int getOwnerIdOf(File file) { - return NO_USER_ID; - } - - /** - * {@inheritDoc} - */ - public void setPermissions(File file, int permissions) { - /* do nothing */ - } - - /** - * {@inheritDoc} - */ - public void incrementSemaphore(File directory, String name, boolean global, int count) { - AttachmentHandle handle = openSemaphore(directory, name, global); - try { - while (count-- > 0) { - if (!library.ReleaseSemaphore(handle.getHandle(), 1, null)) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - } - } finally { - handle.close(); - } - } - - /** - * {@inheritDoc} - */ - public void decrementSemaphore(File directory, String name, boolean global, int count) { - AttachmentHandle handle = openSemaphore(directory, name, global); - try { - while (count-- > 0) { - int result = Kernel32.INSTANCE.WaitForSingleObject(handle.getHandle(), 0); - switch (result) { - case WinBase.WAIT_ABANDONED: - case WinBase.WAIT_OBJECT_0: - break; - case WinError.WAIT_TIMEOUT: - return; - default: - throw new Win32Exception(result); - } - } - } finally { - handle.close(); - } - } - - /** - * Opens a semaphore for signaling another process that an attachment is performed. - * - * @param directory The control directory. - * @param name The semaphore's name. - * @param global {@code true} if the semaphore is in the global namespace. - * @return A handle for signaling an attachment to the target process. - */ - private AttachmentHandle openSemaphore(File directory, String name, boolean global) { - WinNT.SECURITY_DESCRIPTOR securityDescriptor = new WinNT.SECURITY_DESCRIPTOR(64 * 1024); - try { - if (!Advapi32.INSTANCE.InitializeSecurityDescriptor(securityDescriptor, WinNT.SECURITY_DESCRIPTOR_REVISION)) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - if (!Advapi32.INSTANCE.SetSecurityDescriptorDacl(securityDescriptor, true, null, true)) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - WindowsLibrary.SecurityAttributes securityAttributes = new WindowsLibrary.SecurityAttributes(); - try { - securityAttributes.length = new WinDef.DWORD(securityAttributes.size()); - securityAttributes.securityDescriptor = securityDescriptor.getPointer(); - WinNT.HANDLE mutex = library.CreateMutex(securityAttributes, false, CREATION_MUTEX_NAME); - if (mutex == null) { - int lastError = Kernel32.INSTANCE.GetLastError(); - if (lastError == WinError.ERROR_ALREADY_EXISTS) { - mutex = library.OpenMutex(WinNT.STANDARD_RIGHTS_REQUIRED | WinNT.SYNCHRONIZE | 0x0001, false, CREATION_MUTEX_NAME); - if (mutex == null) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); + boolean global = Boolean.parseBoolean(target.getProperty("globalSemaphore")); + dispatcher.incrementSemaphore(directory, "_notifier", global, notifications); + try { + Socket socket = serverSocket.accept(); + String answer = new String(read(socket), "UTF-8"); + if (answer.contains(' ' + key + ' ')) { + return new ForOpenJ9(socket); + } else { + socket.close(); + throw new IllegalStateException("Unexpected answered to attachment: " + answer); } - } else { - throw new Win32Exception(lastError); + } finally { + dispatcher.decrementSemaphore(directory, "_notifier", global, notifications); } - } - int result = Kernel32.INSTANCE.WaitForSingleObject(mutex, 2000); - switch (result) { - case WinBase.WAIT_FAILED: - case WinError.WAIT_TIMEOUT: - throw new Win32Exception(result); - default: + } finally { + for (Map.Entry entry : locks.entrySet()) { try { - String target = (global ? "Global\\" : "") - + (directory.getAbsolutePath() + '_' + name).replaceAll("[^a-zA-Z0-9_]", "") - + "_semaphore"; - WinNT.HANDLE parent = library.OpenSemaphoreW(WindowsLibrary.SEMAPHORE_ALL_ACCESS, false, target); - if (parent == null) { - parent = library.CreateSemaphoreW(null, 0, Integer.MAX_VALUE, target); - if (parent == null) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - WinNT.HANDLE child = library.CreateSemaphoreW(null, 0, Integer.MAX_VALUE, target + "_set0"); - if (child == null) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - return new AttachmentHandle(parent, child); - } else { - WinNT.HANDLE child = library.OpenSemaphoreW(WindowsLibrary.SEMAPHORE_ALL_ACCESS, false, target + "_set0"); - if (child == null) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - return new AttachmentHandle(parent, child); - } - } finally { - if (!library.ReleaseMutex(mutex)) { - throw new Win32Exception(Native.getLastError()); + try { + entry.getValue().release(); + } finally { + entry.getKey().close(); } + } catch (Throwable ignored) { + /* do nothing */ } + } } } finally { - securityAttributes = null; + if (!reply.delete()) { + reply.deleteOnExit(); + } } } finally { - securityDescriptor = null; + serverSocket.close(); } + } finally { + attachLockLock.release(); } + } finally { + attachLock.close(); + } + } - /** - * A library for interacting with Windows. - */ - protected interface WindowsLibrary extends StdCallLibrary { + /** + * {@inheritDoc} + */ + public Properties getSystemProperties() throws IOException { + write(socket, "ATTACH_GETSYSTEMPROPERTIES".getBytes("UTF-8")); + Properties properties = new Properties(); + properties.load(new ByteArrayInputStream(read(socket))); + return properties; + } - /** - * Indicates that a semaphore requires all access rights. - */ - int SEMAPHORE_ALL_ACCESS = 0x1F0003; + /** + * {@inheritDoc} + */ + public Properties getAgentProperties() throws IOException { + write(socket, "ATTACH_GETAGENTPROPERTIES".getBytes("UTF-8")); + Properties properties = new Properties(); + properties.load(new ByteArrayInputStream(read(socket))); + return properties; + } - /** - * Opens an existing semaphore. - * - * @param access The access rights. - * @param inheritHandle {@code true} if the handle is inherited. - * @param name The semaphore's name. - * @return The handle or {@code null} if the handle could not be created. - */ - @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE OpenSemaphoreW(int access, boolean inheritHandle, String name); + /** + * {@inheritDoc} + */ + public void loadAgent(String jarFile, String argument) throws IOException { + write(socket, ("ATTACH_LOADAGENT(instrument," + jarFile + '=' + (argument == null ? "" : argument) + ')').getBytes("UTF-8")); + String answer = new String(read(socket), "UTF-8"); + if (answer.startsWith("ATTACH_ERR")) { + throw new IllegalStateException("Target VM failed loading agent: " + answer); + } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { + throw new IllegalStateException("Unexpected response: " + answer); + } + } - /** - * Creates a new semaphore. - * - * @param securityAttributes The security attributes for the created semaphore. - * @param count The initial count for the semaphore. - * @param maximumCount The maximum count for the semaphore. - * @param name The semaphore's name. - * @return The handle or {@code null} if the handle could not be created. - */ - @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE CreateSemaphoreW(WinBase.SECURITY_ATTRIBUTES securityAttributes, long count, long maximumCount, String name); + /** + * {@inheritDoc} + */ + public void loadAgentPath(String path, String argument) throws IOException { + write(socket, ("ATTACH_LOADAGENTPATH(" + path + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); + String answer = new String(read(socket), "UTF-8"); + if (answer.startsWith("ATTACH_ERR")) { + throw new IllegalStateException("Target VM failed loading native agent: " + answer); + } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { + throw new IllegalStateException("Unexpected response: " + answer); + } + } - /** - * Releases the semaphore. - * - * @param handle The semaphore's handle. - * @param count The amount with which to increase the semaphore. - * @param previousCount The previous count of the semaphore or {@code null}. - * @return {@code true} if the semaphore was successfully released. - */ - @SuppressWarnings("checkstyle:methodname") - boolean ReleaseSemaphore(WinNT.HANDLE handle, long count, Long previousCount); + /** + * {@inheritDoc} + */ + public void loadAgentLibrary(String library, String argument) throws IOException { + write(socket, ("ATTACH_LOADAGENTLIBRARY(" + library + (argument == null ? "" : (',' + argument)) + ')').getBytes("UTF-8")); + String answer = new String(read(socket), "UTF-8"); + if (answer.startsWith("ATTACH_ERR")) { + throw new IllegalStateException("Target VM failed loading native library: " + answer); + } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { + throw new IllegalStateException("Unexpected response: " + answer); + } + } - /** - * Create or opens a mutex. - * - * @param attributes The mutex's security attributes. - * @param owner {@code true} if the caller is supposed to be the initial owner. - * @param name The mutex name. - * @return The handle to the mutex or {@code null} if the mutex could not be created. - */ - @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE CreateMutex(SecurityAttributes attributes, boolean owner, String name); + /** + * {@inheritDoc} + */ + public void startManagementAgent(Properties properties) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + properties.store(outputStream, null); + write(socket, "ATTACH_START_MANAGEMENT_AGENT".getBytes("UTF-8")); + write(socket, outputStream.toByteArray()); + String answer = new String(read(socket), "UTF-8"); + if (answer.startsWith("ATTACH_ERR")) { + throw new IllegalStateException("Target VM could not start management agent: " + answer); + } else if (!answer.startsWith("ATTACH_ACK") && !answer.startsWith("ATTACH_RESULT=")) { + throw new IllegalStateException("Unexpected response: " + answer); + } + } - /** - * Opens an existing object. - * - * @param access The required access privileges. - * @param inherit {@code true} if the mutex should be inherited. - * @param name The mutex's name. - * @return The handle or {@code null} if the mutex could not be opened. - */ - @SuppressWarnings("checkstyle:methodname") - WinNT.HANDLE OpenMutex(int access, boolean inherit, String name); + /** + * {@inheritDoc} + */ + public String startLocalManagementAgent() throws IOException { + write(socket, "ATTACH_START_LOCAL_MANAGEMENT_AGENT".getBytes("UTF-8")); + String answer = new String(read(socket), "UTF-8"); + if (answer.startsWith("ATTACH_ERR")) { + throw new IllegalStateException("Target VM could not start management agent: " + answer); + } else if (answer.startsWith("ATTACH_ACK")) { + return answer.substring("ATTACH_ACK".length()); + } else if (answer.startsWith("ATTACH_RESULT=")) { + return answer.substring("ATTACH_RESULT=".length()); + } else { + throw new IllegalStateException("Unexpected response: " + answer); + } + } - /** - * Releases the supplied mutex. - * - * @param handle The handle to the mutex. - * @return {@code true} if the handle was successfully released. - */ - @SuppressWarnings("checkstyle:methodname") - boolean ReleaseMutex(WinNT.HANDLE handle); + /** + * {@inheritDoc} + */ + public void detach() throws IOException { + try { + write(socket, "ATTACH_DETACH".getBytes("UTF-8")); + read(socket); // The answer is intentionally ignored. + } finally { + socket.close(); + } + } - /** - * A structure representing a mutex's security attributes. - */ - @SuppressFBWarnings(value = {"URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", "UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"}, justification = "Field required by native implementation.") - class SecurityAttributes extends Structure { - - /** - * The descriptor's length. - */ - public WinDef.DWORD length; - - /** - * A pointer to the descriptor. - */ - public Pointer securityDescriptor; - - /** - * {@code true} if the attributes are inherited. - */ - @SuppressWarnings("unused") - public boolean inherit; - - @Override - protected List getFieldOrder() { - return Arrays.asList("length", "securityDescriptor", "inherit"); - } - } + /** + * Writes the supplied value to the target socket. + * + * @param socket The socket to write to. + * @param value The value being written. + * @throws IOException If an I/O exception occurs. + */ + private static void write(Socket socket, byte[] value) throws IOException { + socket.getOutputStream().write(value); + socket.getOutputStream().write(0); + socket.getOutputStream().flush(); + } + + /** + * Reads a {@code '\0'}-terminated value from the target socket. + * + * @param socket The socket to read from. + * @return The value that was read. + * @throws IOException If an I/O exception occurs. + */ + private static byte[] read(Socket socket) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = socket.getInputStream().read(buffer)) != -1) { + if (length > 0 && buffer[length - 1] == 0) { + outputStream.write(buffer, 0, length - 1); + break; + } else { + outputStream.write(buffer, 0, length); } + } + return outputStream.toByteArray(); + } - /** - * A handle for an attachment which is represented by a pair of handles. - */ - protected static class AttachmentHandle implements Closeable { + /** + * A dispatcher for native operations being used for communication with an OpenJ9 virtual machine. + */ + public interface Dispatcher { - /** - * The parent handle. - */ - private final WinNT.HANDLE parent; + /** + * Returns this machine's temporary folder. + * + * @return The temporary folder. + */ + String getTemporaryFolder(); - /** - * The child handle. - */ - private final WinNT.HANDLE child; + /** + * Returns the process id of this process. + * + * @return The process id of this process. + */ + int pid(); - /** - * Creates a new attachment handle. - * - * @param parent The parent handle. - * @param child The child handle. - */ - protected AttachmentHandle(WinNT.HANDLE parent, WinNT.HANDLE child) { - this.parent = parent; - this.child = child; - } + /** + * Returns the user id of this process. + * + * @return The user id of this process + */ + int userId(); - /** - * Returns the handle on which signals are to be sent. - * - * @return The handle on which signals are to be sent. - */ - protected WinNT.HANDLE getHandle() { - return child; - } + /** + * Returns {@code true} if the supplied process id is a running process. + * + * @param processId The process id to evaluate. + * @return {@code true} if the supplied process id is currently running. + */ + boolean isExistingProcess(int processId); - /** - * {@inheritDoc} - */ - public void close() { - boolean closed; - try { - if (!Kernel32.INSTANCE.CloseHandle(child)) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - } finally { - closed = Kernel32.INSTANCE.CloseHandle(parent); - } - if (!closed) { - throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); - } - } - } - } + /** + * 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); + + /** + * Sets permissions for the supplied file. + * + * @param file The file for which to set the permissions. + * @param permissions The permission bits to set. + */ + void setPermissions(File file, int permissions); + + /** + * Increments a semaphore. + * + * @param directory The sempahore's control directory. + * @param name The semaphore's name. + * @param global {@code true} if the semaphore is in the global namespace (only applicable on Windows). + * @param count The amount of increments. + */ + void incrementSemaphore(File directory, String name, boolean global, int count); + + /** + * Decrements a semaphore. + * + * @param directory The sempahore's control directory. + * @param name The semaphore's name. + * @param global {@code true} if the semaphore is in the global namespace (only applicable on Windows). + * @param count The amount of decrements. + */ + void decrementSemaphore(File directory, String name, boolean global, int count); } } } -- 2.26.2