/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.agent;

import com.sun.jna.LastErrorException;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinError;
import com.sun.jna.platform.win32.WinNT;
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 net.bytebuddy.agent.utility.nullability.MaybeNull;
import net.bytebuddy.agent.utility.nullability.UnknownNull;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.FileLock;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 * An implementation for attachment on a virtual machine. This interface mimics the tooling API's virtual
 * machine interface to allow for similar usage by {@link ByteBuddyAgent} where all calls are made via
 * reflection such that this structural typing suffices for interoperability.
 * </p>
 * <p>
 * <b>Note</b>: Implementations are required to declare a static method {@code attach(String)} returning an
 * instance of a class that declares the methods defined by {@link VirtualMachine}.
 * </p>
 */
public interface VirtualMachine {

    /**
     * Loads the target VMs system properties.
     *
     * @return The target VM properties.
     * @throws IOException If an I/O exception occurs.
     */
    Properties getSystemProperties() throws IOException;

    /**
     * Loads the target VMs agent properties.
     *
     * @return The target VM properties.
     * @throws IOException If an I/O exception occurs.
     */
    Properties getAgentProperties() throws IOException;

    /**
     * Loads an agent into the represented virtual machine.
     *
     * @param jarFile The jar file to attach.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgent(String jarFile) throws IOException;

    /**
     * Loads an agent into the represented virtual machine.
     *
     * @param jarFile  The jar file to attach.
     * @param argument The argument to provide or {@code null} if no argument should be provided.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgent(String jarFile, @MaybeNull String argument) throws IOException;

    /**
     * Loads a native agent into the represented virtual machine.
     *
     * @param path The agent path.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgentPath(String path) throws IOException;

    /**
     * Loads a native agent into the represented virtual machine.
     *
     * @param path     The agent path.
     * @param argument The argument to provide or {@code null} if no argument should be provided.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgentPath(String path, @MaybeNull String argument) throws IOException;

    /**
     * Loads a native agent library into the represented virtual machine.
     *
     * @param library The agent library.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgentLibrary(String library) throws IOException;

    /**
     * Loads a native agent library into the represented virtual machine.
     *
     * @param library  The agent library.
     * @param argument The argument to provide or {@code null} if no argument should be provided.
     * @throws IOException If an I/O exception occurs.
     */
    void loadAgentLibrary(String library, @MaybeNull String argument) throws IOException;

    /**
     * Starts a JMX management agent.
     *
     * @param properties The properties to transfer to the JMX agent.
     * @throws IOException If an I/O error occurs.
     */
    void startManagementAgent(Properties properties) throws IOException;

    /**
     * Starts a local management agent.
     *
     * @return The local connector address.
     * @throws IOException If an I/O error occurs.
     */
    String startLocalManagementAgent() throws IOException;

    /**
     * Detaches this virtual machine representation.
     *
     * @throws IOException If an I/O exception occurs.
     */
    void detach() throws IOException;

    /**
     * A resolver for the current VM's virtual machine attachment emulation.
     */
    enum Resolver implements PrivilegedAction<Class<? extends VirtualMachine>> {

        /**
         * The singleton instance.
         */
        INSTANCE;

        /**
         * {@inheritDoc}
         */
        public Class<? extends VirtualMachine> run() {
            try {
                Class.forName("com.sun.jna.Platform");
            } catch (ClassNotFoundException exception) {
                throw new IllegalStateException("Optional JNA dependency is not available", exception);
            }
            return System.getProperty("java.vm.name", "").toUpperCase(Locale.US).contains("J9")
                    ? ForOpenJ9.class
                    : ForHotSpot.class;

        }
    }

    /**
     * An abstract base implementation for a virtual machine.
     */
    abstract class AbstractBase implements VirtualMachine {

        /**
         * {@inheritDoc}
         */
        public void loadAgent(String jarFile) throws IOException {
            loadAgent(jarFile, null);
        }

        /**
         * {@inheritDoc}
         */
        public void loadAgentPath(String path) throws IOException {
            loadAgentPath(path, null);
        }

        /**
         * {@inheritDoc}
         */
        public void loadAgentLibrary(String library) throws IOException {
            loadAgentLibrary(library, null);
        }
    }

    /**
     * A virtual machine attachment implementation for a HotSpot VM or any compatible JVM.
     */
    class ForHotSpot extends AbstractBase {

        /**
         * The protocol version to use for communication.
         */
        private static final String PROTOCOL_VERSION = "1";

        /**
         * The {@code load} command.
         */
        private static final String LOAD_COMMAND = "load";

        /**
         * The {@code instrument} command.
         */
        private static final String INSTRUMENT_COMMAND = "instrument";

        /**
         * A delimiter to be used for attachment.
         */
        private static final String ARGUMENT_DELIMITER = "=";

        /**
         * The virtual machine connection.
         */
        private final Connection connection;

        /**
         * Creates a new virtual machine connection for HotSpot.
         *
         * @param connection The virtual machine connection.
         */
        protected ForHotSpot(Connection connection) {
            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.
         *
         * @param processId         The process id.
         * @param connectionFactory The connection factory to use.
         * @return A suitable virtual machine implementation.
         * @throws IOException If an IO exception occurs during establishing the connection.
         */
        public static VirtualMachine attach(String processId, Connection.Factory connectionFactory) throws IOException {
            return new ForHotSpot(connectionFactory.connect(processId));
        }

        /**
         * Checks the header of a response.
         *
         * @param response The response to check the header for.
         * @throws IOException If an I/O exception occurs.
         */
        private static void checkHeader(Connection.Response response) throws IOException {
            byte[] buffer = new byte[1];
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            int length;
            while ((length = response.read(buffer)) != -1) {
                if (length > 0) {
                    if (buffer[0] == '\n') {
                        break;
                    }
                    outputStream.write(buffer[0]);
                }
            }
            switch (Integer.parseInt(outputStream.toString("UTF-8"))) {
                case 0:
                    return;
                case 101:
                    throw new IOException("Protocol mismatch with target VM");
                default:
                    buffer = new byte[1024];
                    outputStream = new ByteArrayOutputStream();
                    while ((length = response.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, length);
                    }
                    throw new IllegalStateException(outputStream.toString("UTF-8"));
            }
        }

        /**
         * {@inheritDoc}
         */
        public Properties getSystemProperties() throws IOException {
            return getProperties("properties");
        }

        /**
         * {@inheritDoc}
         */
        public Properties getAgentProperties() throws IOException {
            return getProperties("agentProperties");
        }

        /**
         * Loads properties of the target VM.
         *
         * @param command The command for fetching properties.
         * @return The read properties.
         * @throws IOException If an I/O exception occurs.
         */
        private Properties getProperties(String command) throws IOException {
            Connection.Response response = connection.execute(PROTOCOL_VERSION, command, null, null, null);
            try {
                checkHeader(response);
                byte[] buffer = new byte[1024];
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                int length;
                while ((length = response.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
                Properties properties = new Properties();
                properties.load(new ByteArrayInputStream(outputStream.toByteArray()));
                return properties;
            } finally {
                response.close();
            }
        }

        /**
         * {@inheritDoc}
         */
        public void loadAgent(String jarFile, @MaybeNull String argument) throws IOException {
            load(jarFile, false, argument);
        }

        /**
         * {@inheritDoc}
         */
        public void loadAgentPath(String path, @MaybeNull String argument) throws IOException {
            load(path, true, argument);
        }

        /**
         * {@inheritDoc}
         */
        public void loadAgentLibrary(String library, @MaybeNull String argument) throws IOException {
            load(library, false, argument);
        }

        /**
         * Loads an agent by the given command.
         *
         * @param file     The Java agent or library to be loaded.
         * @param absolute {@code true} if the agent location is absolute.
         * @param argument The argument to the agent or {@code null} if no argument is given.
         * @throws IOException If an I/O exception occurs.
         */
        protected void load(String file, boolean absolute, @MaybeNull String argument) throws IOException {
            Connection.Response response = connection.execute(PROTOCOL_VERSION, LOAD_COMMAND, INSTRUMENT_COMMAND, Boolean.toString(absolute), (argument == null
                    ? file
                    : file + ARGUMENT_DELIMITER + argument));
            try {
                checkHeader(response);
            } finally {
                response.close();
            }
        }

        /**
         * {@inheritDoc}
         */
        public void startManagementAgent(Properties properties) throws IOException {
            StringBuilder stringBuilder = new StringBuilder("ManagementAgent.start ");
            boolean first = true;
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                if (!(entry.getKey() instanceof String) || !((String) entry.getKey()).startsWith("com.sun.management.")) {
                    throw new IllegalArgumentException("Illegal property name: " + entry.getKey());
                } else if (first) {
                    first = false;
                } else {
                    stringBuilder.append(' ');
                }
                stringBuilder.append(((String) entry.getKey()).substring("com.sun.management.".length())).append('=');
                String value = entry.getValue().toString();
                if (value.contains(" ")) {
                    stringBuilder.append('\'').append(value).append('\'');
                } else {
                    stringBuilder.append(value);
                }
            }
            Connection.Response response = connection.execute(PROTOCOL_VERSION, "jcmd", stringBuilder.toString(), null, null);
            try {
                checkHeader(response);
            } finally {
                response.close();
            }
        }

        /**
         * {@inheritDoc}
         */
        public String startLocalManagementAgent() throws IOException {
            Connection.Response response = connection.execute(PROTOCOL_VERSION, "jcmd", "ManagementAgent.start_local", null, null);
            try {
                checkHeader(response);
                return getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
            } finally {
                response.close();
            }
        }

        /**
         * {@inheritDoc}
         */
        public void detach() throws IOException {
            connection.close();
        }

        /**
         * Represents a connection to a virtual machine.
         */
        public interface Connection extends Closeable {

            /**
             * Executes a command on the current connection.
             *
             * @param protocol The target VMs protocol version for the attach API.
             * @param argument The arguments to send to the target VM.
             * @return The response of the target JVM.
             * @throws IOException If an I/O error occurred.
             */
            Response execute(String protocol, String... argument) throws IOException;

            /**
             * A response to an execution command to a VM.
             */
            interface Response extends Closeable {

                /**
                 * Reads a buffer from the target VM.
                 *
                 * @param buffer The buffer to read to.
                 * @return The bytes read or {@code -1} if no more bytes could be read.
                 * @throws IOException If an I/O exception occurred.
                 */
                int read(byte[] buffer) throws IOException;
            }

            /**
             * A factory for creating connections to virtual machines.
             */
            interface Factory {

                /**
                 * Connects to the supplied process.
                 *
                 * @param processId The process id.
                 * @return The connection to the virtual machine with the supplied process id.
                 * @throws IOException If an I/O exception occurs during connecting to the targeted VM.
                 */
                Connection connect(String processId) throws IOException;

                /**
                 * A factory for attaching via a socket file.
                 */
                abstract class ForSocketFile implements Factory {

                    /**
                     * The name prefix for a socket.
                     */
                    private static final String SOCKET_FILE_PREFIX = ".java_pid";

                    /**
                     * The name prefix for an attachment file indicator.
                     */
                    private static final String ATTACH_FILE_PREFIX = ".attach_pid";

                    /**
                     * The temporary directory to use.
                     */
                    private final String temporaryDirectory;

                    /**
                     * The maximum amount of attempts for checking the establishment of a socket connection.
                     */
                    private final int attempts;

                    /**
                     * The pause between two checks for an established socket connection.
                     */
                    private final long pause;

                    /**
                     * The time unit of the pause time.
                     */
                    private final TimeUnit timeUnit;

                    /**
                     * Creates a connection factory for creating a socket connection via a file.
                     *
                     * @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.
                     */
                    protected ForSocketFile(String temporaryDirectory, int attempts, long pause, TimeUnit timeUnit) {
                        this.temporaryDirectory = temporaryDirectory;
                        this.attempts = attempts;
                        this.pause = pause;
                        this.timeUnit = timeUnit;
                    }

                    /**
                     * {@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()) {
                            String target = ATTACH_FILE_PREFIX + processId, path = "/proc/" + processId + "/cwd/" + target;
                            File attachFile = new File(path);
                            try {
                                if (!attachFile.createNewFile() && !attachFile.isFile()) {
                                    throw new IllegalStateException("Could not create attach file: " + attachFile);
                                }
                            } catch (IOException ignored) {
                                attachFile = new File(temporaryDirectory, target);
                                if (!attachFile.createNewFile() && !attachFile.isFile()) {
                                    throw new IllegalStateException("Could not create attach file: " + attachFile);
                                }
                            }
                            try {
                                kill(processId, 3);
                                int attempts = this.attempts;
                                while (!socket.exists() && attempts-- > 0) {
                                    timeUnit.sleep(pause);
                                }
                                if (!socket.exists()) {
                                    throw new IllegalStateException("Target VM did not respond: " + processId);
                                }
                            } catch (InterruptedException exception) {
                                Thread.currentThread().interrupt();
                                throw new IllegalStateException(exception);
                            } finally {
                                if (!attachFile.delete()) {
                                    attachFile.deleteOnExit();
                                }
                            }
                        }
                        return doConnect(socket);
                    }

                    /**
                     * Sends a kill signal to the target process.
                     *
                     * @param processId The process id.
                     * @param signal    The signal to send.
                     */
                    protected abstract void kill(String processId, int signal);

                    /**
                     * Connects to the supplied POSIX socket.
                     *
                     * @param socket The socket to connect to.
                     * @return An active connection to the supplied socket.
                     * @throws IOException If an error occurs during connection.
                     */
                    protected abstract Connection doConnect(File socket) throws IOException;
                }
            }

            /**
             * A connection that is represented by a byte channel that is persistent during communication.
             *
             * @param <T> The connection representation.
             */
            abstract class OnPersistentByteChannel<T> implements Connection {

                /**
                 * A blank line argument.
                 */
                private static final byte[] BLANK = new byte[]{0};

                /**
                 * {@inheritDoc}
                 */
                public Connection.Response execute(String protocol, String... argument) throws IOException {
                    T connection = connect();
                    try {
                        write(connection, protocol.getBytes("UTF-8"));
                        write(connection, BLANK);
                        for (String anArgument : argument) {
                            if (anArgument != null) {
                                write(connection, anArgument.getBytes("UTF-8"));
                            }
                            write(connection, BLANK);
                        }
                        return new Response(connection);
                    } catch (Throwable throwable) {
                        close(connection);
                        if (throwable instanceof RuntimeException) {
                            throw (RuntimeException) throwable;
                        } else if (throwable instanceof IOException) {
                            throw (IOException) throwable;
                        } else {
                            throw new IllegalStateException(throwable);
                        }
                    }
                }

                /**
                 * A response of a persistent byte channel.
                 */
                private class Response implements Connection.Response {

                    /**
                     * The connection representing this response.
                     */
                    private final T connection;

                    /**
                     * Creates a new response for a persistent byte channel.
                     *
                     * @param connection The connection representing this response.
                     */
                    private Response(T connection) {
                        this.connection = connection;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public int read(byte[] buffer) throws IOException {
                        return OnPersistentByteChannel.this.read(connection, buffer);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void close() throws IOException {
                        OnPersistentByteChannel.this.close(connection);
                    }
                }

                /**
                 * Creates a new connection to the target VM.
                 *
                 * @return Returns a new connection to the target VM.
                 * @throws IOException If an I/O exception occurs.
                 */
                protected abstract T connect() throws IOException;

                /**
                 * Closes the connection to the target VM.
                 *
                 * @param connection The connection to close.
                 * @throws IOException If an I/O exception occurs.
                 */
                protected abstract void close(T connection) throws IOException;

                /**
                 * Writes to the target VM.
                 *
                 * @param connection The connection to write to.
                 * @param buffer     The buffer to write to.
                 * @throws IOException If an I/O exception occurs during writing.
                 */
                protected abstract void write(T connection, byte[] buffer) throws IOException;

                /**
                 * Reads from the target VM.
                 *
                 * @param connection The connection to read from.
                 * @param buffer     The buffer to store the result in.
                 * @return The number of byte that were read.
                 * @throws IOException If an I/O exception occurs.
                 */
                protected abstract int read(T connection, byte[] buffer) throws IOException;
            }

            /**
             * Implements a connection for a POSIX socket in JNA.
             */
            class ForJnaPosixSocket extends OnPersistentByteChannel<Integer> {

                /**
                 * The JNA library to use.
                 */
                private final PosixLibrary library;

                /**
                 * The POSIX socket.
                 */
                private final File 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;
                }

                @Override
                protected Integer connect() {
                    int handle = library.socket(1, 1, 0);
                    try {
                        PosixLibrary.SocketAddress address = new PosixLibrary.SocketAddress();
                        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);
                            }
                        }

                        @Override
                        protected List<String> 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);
                                }
                            } finally {
                                memory = null;
                            }
                        } else {
                            temporaryDirectory = "/tmp";
                        }
                        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);
                    }
                }
            }

            /**
             * 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));
                    }
                    String name = "\\\\.\\pipe\\javatool" + Math.abs(random.nextInt() + 1);

                    WinBase.SECURITY_ATTRIBUTES sa = createSecurityAttributesToAllowMediumIntegrity();

                    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,
                            sa);
                    if (pipe == null) {
                        throw new Win32Exception(Native.getLastError());
                    }
                    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());
                        }
                        try {
                            WinNT.HANDLE thread = library.CreateRemoteThread(process, null, 0, code.getPointer(), data.getPointer(), null, null);
                            if (thread == null) {
                                throw new Win32Exception(Native.getLastError());
                            }
                            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 VM could not dispatch command successfully: " + exitCode.getValue());
                                }
                                if (!Kernel32.INSTANCE.ConnectNamedPipe(pipe, null)) {
                                    int code = Native.getLastError();
                                    if (code != WinError.ERROR_PIPE_CONNECTED) {
                                        throw new Win32Exception(code);
                                    }
                                }
                                return new NamedPipeResponse(pipe);
                            } 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());
                            }
                        }
                    } 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);
                        }
                    }
                }

                /**
                 * Custom {@link WinBase.SECURITY_ATTRIBUTES} is required here to "get" Medium Integrity Level.
                 * In order to allow Medium Integrity Level clients to open
                 * and use a NamedPipe created by an High Integrity Level process.
                 *
                 * @return A security attributes object that gives everyone read and write access.
                 */
                private WinBase.SECURITY_ATTRIBUTES createSecurityAttributesToAllowMediumIntegrity() {
                    // Allow read/write to Everybody
                    WinNT.PSID pSidEverybody = new WinNT.PSID(WinNT.SECURITY_MAX_SID_SIZE);
                    if (!Advapi32.INSTANCE.CreateWellKnownSid(WinNT.WELL_KNOWN_SID_TYPE.WinWorldSid, null, pSidEverybody, new IntByReference(WinNT.SECURITY_MAX_SID_SIZE))) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    // Allow full control to System
                    WinNT.PSID pSidSystem = new WinNT.PSID(WinNT.SECURITY_MAX_SID_SIZE);
                    if (!Advapi32.INSTANCE.CreateWellKnownSid(WinNT.WELL_KNOWN_SID_TYPE.WinBuiltinSystemOperatorsSid, null, pSidSystem, new IntByReference(WinNT.SECURITY_MAX_SID_SIZE))) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    // Allow full control to Administrators
                    WinNT.PSID pSidAdmin = new WinNT.PSID(WinNT.SECURITY_MAX_SID_SIZE);
                    if (!Advapi32.INSTANCE.CreateWellKnownSid(WinNT.WELL_KNOWN_SID_TYPE.WinBuiltinAdministratorsSid, null, pSidAdmin, new IntByReference(WinNT.SECURITY_MAX_SID_SIZE))) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }

                    // Prepare ACL
                    int cbAcl = Native.getNativeSize(WinNT.ACL.class, null);
                    cbAcl += Native.getNativeSize(WinNT.ACCESS_ALLOWED_ACE.class, null) * 3;
                    cbAcl += (Advapi32.INSTANCE.GetLengthSid(pSidEverybody) - WinDef.DWORD.SIZE);
                    cbAcl += (Advapi32.INSTANCE.GetLengthSid(pSidSystem) - WinDef.DWORD.SIZE);
                    cbAcl += (Advapi32.INSTANCE.GetLengthSid(pSidAdmin) - WinDef.DWORD.SIZE);
                    cbAcl = Advapi32Util.alignOnDWORD(cbAcl);
                    WinNT.ACL pAcl = new WinNT.ACL(cbAcl);
                    if (!Advapi32.INSTANCE.InitializeAcl(pAcl, cbAcl, WinNT.ACL_REVISION)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    if (!Advapi32.INSTANCE.AddAccessAllowedAce(pAcl, WinNT.ACL_REVISION, WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, pSidEverybody)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    if (!Advapi32.INSTANCE.AddAccessAllowedAce(pAcl, WinNT.ACL_REVISION, WinNT.GENERIC_ALL, pSidSystem)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    if (!Advapi32.INSTANCE.AddAccessAllowedAce(pAcl, WinNT.ACL_REVISION, WinNT.GENERIC_ALL, pSidAdmin)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    WinNT.SECURITY_DESCRIPTOR sd = new WinNT.SECURITY_DESCRIPTOR(64 * 1024);
                    if (!Advapi32.INSTANCE.InitializeSecurityDescriptor(sd, WinNT.SECURITY_DESCRIPTOR_REVISION)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    if (!Advapi32.INSTANCE.SetSecurityDescriptorDacl(sd, true, pAcl, false)) {
                        throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
                    }
                    WinBase.SECURITY_ATTRIBUTES sa = new WinBase.SECURITY_ATTRIBUTES();
                    sa.dwLength = new WinDef.DWORD(sa.size());
                    sa.lpSecurityDescriptor = sd.getPointer();
                    return sa;
                }

                /**
                 * {@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.
                     */
                    @MaybeNull
                    @SuppressWarnings("checkstyle:methodname")
                    WinNT.HANDLE CreateRemoteThread(WinNT.HANDLE process,
                                                    @MaybeNull WinBase.SECURITY_ATTRIBUTES securityAttributes,
                                                    int stackSize,
                                                    Pointer code,
                                                    Pointer argument,
                                                    @MaybeNull WinDef.DWORD creationFlags,
                                                    @MaybeNull 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.
                     */
                    @MaybeNull
                    @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.
                     */
                    @MaybeNull
                    @SuppressWarnings("checkstyle:methodname")
                    WinDef.LPVOID allocate_remote_argument(WinNT.HANDLE process,
                                                           String pipe,
                                                           @MaybeNull String argument0,
                                                           @MaybeNull String argument1,
                                                           @MaybeNull String argument2,
                                                           @MaybeNull 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.
                         */
                        @MaybeNull
                        public Pointer dataPointer;

                        /**
                         * The size of the argument being pointed to.
                         */
                        public int dataSize;

                        /**
                         * A pointer to the operation descriptor.
                         */
                        @MaybeNull
                        public Pointer descriptorPointer;

                        /**
                         * The size of the operation argument.
                         */
                        public int descriptorCount;

                        /**
                         * A pointer to the operation result.
                         */
                        @UnknownNull
                        public Pointer resultPointer;

                        /**
                         * The size of the operation argument.
                         */
                        public int resultSize;

                        @Override
                        protected List<String> 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";

        /**
         * A secure random for generating randomized ids.
         */
        private static final SecureRandom SECURE_RANDOM = new SecureRandom();

        /**
         * 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. This method will not consider
         * attaching to VMs owned by different users than the current user.
         *
         * @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 {
            String temporary = System.getProperty(IBM_TEMPORARY_FOLDER);
            File directory = temporary == null
                    ? new File(dispatcher.getTemporaryFolder(processId), ".com_ibm_tools_attach")
                    : new File(temporary);
            long userId = dispatcher.userId();
            RandomAccessFile attachLock = new RandomAccessFile(new File(directory, "_attachlock"), "rw");
            try {
                FileLock attachLockLock = attachLock.getChannel().lock();
                try {
                    List<Properties> 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);
                            }
                            virtualMachines = new ArrayList<Properties>();
                            for (File aVmFolder : vmFolder) {
                                if (aVmFolder.isDirectory() && (userId == 0L || 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;
                        synchronized (SECURE_RANDOM) {
                            key = Long.toHexString(SECURE_RANDOM.nextLong());
                        }
                        File reply = new File(receiver, "replyInfo");
                        long targetUserId;
                        try {
                            targetUserId = Long.parseLong(target.getProperty("userUid"));
                        } catch (NumberFormatException ignored) {
                            targetUserId = 0L;
                        }
                        try {
                            if (reply.createNewFile()) {
                                dispatcher.setPermissions(reply, 0600);
                            }
                            if (userId == 0L && targetUserId != 0L) {
                                dispatcher.chownFileToUser(reply, targetUserId);
                            }
                            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<RandomAccessFile, FileLock> locks = new HashMap<RandomAccessFile, FileLock>();
                            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<RandomAccessFile, FileLock> 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, @MaybeNull 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, @MaybeNull 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, @MaybeNull 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.
             *
             * @param processId The target process's id.
             * @return The temporary folder.
             */
            String getTemporaryFolder(String processId);

            /**
             * 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);

            /**
             * Changes the ownership of a file. Can be called only if this process is owned by root.
             *
             * @param file   The path of the file to change ownership of.
             * @param userId The user that should own the file.
             */
            void chownFileToUser(File file, long userId);

            /**
             * A connector implementation for a POSIX environment using JNA.
             */
            class ForJnaPosixEnvironment implements Dispatcher {

                /**
                 * The JNA library to use.
                 */
                private final PosixLibrary library;

                /**
                 * The POSIX owner provider to use.
                 */
                private final PosixOwnerProvider provider;

                /**
                 * Creates a new connector for a POSIX environment 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) {
                    provider = Platform.isAIX()
                            ? new PosixOwnerProvider.UsingIStat(attempts, pause, timeUnit)
                            : new PosixOwnerProvider.UsingStat(attempts, pause, timeUnit);
                    library = Native.loadLibrary("c", PosixLibrary.class);
                }

                /**
                 * {@inheritDoc}
                 */
                public String getTemporaryFolder(String processId) {
                    if (Platform.isLinux()) {
                        File file = new File("/proc/" + processId + "/root/tmp");
                        if (file.isDirectory() && file.canRead()) {
                            return file.getAbsolutePath();
                        }
                    }
                    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) {
                    return provider.getOwnerIdOf(file);
                }

                /**
                 * {@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);
                }

                /**
                 * {@inheritDoc}
                 */
                public void chownFileToUser(File file, long userId) {
                    library.chown(file.getAbsolutePath(), userId);
                }

                /**
                 * 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;
                    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;
                            }
                        }
                    }
                }

                /**
                 * 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 chown} command.
                     *
                     * @param path   The file path.
                     * @param userId The user id to set.
                     * @return The return code.
                     * @throws LastErrorException If an error occurred.
                     */
                    int chown(String path, long userId) 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<String> getFieldOrder() {
                            return Arrays.asList("number", "operation", "flags");
                        }
                    }
                }

                /**
                 * Represents a system that supports POSIX ownership.
                 */
                protected interface PosixOwnerProvider {

                    /**
                     * 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);

                    /**
                     * An implementation of reading POSIX ownership using {@code stat}.
                     */
                    class UsingStat implements PosixOwnerProvider {

                        /**
                         * 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 provider where an owner is derived using the {@code stat} command.
                         *
                         * @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 UsingStat(int attempts, long pause, TimeUnit timeUnit) {
                            this.attempts = attempts;
                            this.pause = pause;
                            this.timeUnit = timeUnit;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        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;
                                String line = null;
                                do {
                                    try {
                                        if (process.exitValue() != 0) {
                                            throw new IllegalStateException("Error while executing stat");
                                        }
                                        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
                                        try {
                                            line = reader.readLine();
                                        } finally {
                                            reader.close();
                                        }
                                        break;
                                    } catch (IllegalThreadStateException ignored) {
                                        try {
                                            Thread.sleep(timeUnit.toMillis(pause));
                                        } catch (InterruptedException exception) {
                                            Thread.currentThread().interrupt();
                                            throw new IllegalStateException(exception);
                                        }
                                    }
                                } while (--attempts > 0);
                                if (line == null) {
                                    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);
                            }
                        }
                    }

                    /**
                     * An implementation for reading a POSIX owner using {@code istat}.
                     */
                    class UsingIStat implements PosixOwnerProvider {

                        /**
                         * A pattern to represent the owner on the console output.
                         */
                        private static final Pattern AIX_OWNER_PATTERN = Pattern.compile("Owner: (\\d+)\\(");

                        /**
                         * 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 provider for reading a POSIX owner using {@code istat}.
                         *
                         * @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 UsingIStat(int attempts, long pause, TimeUnit timeUnit) {
                            this.attempts = attempts;
                            this.pause = pause;
                            this.timeUnit = timeUnit;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        public int getOwnerIdOf(File file) {
                            try {
                                Process process = Runtime.getRuntime().exec(new String[]{"istat", file.getAbsolutePath()});
                                int attempts = this.attempts;
                                String lines = null;
                                do {
                                    try {
                                        if (process.exitValue() != 0) {
                                            throw new IllegalStateException("Error while executing istat");
                                        }
                                        StringBuilder output = new StringBuilder();
                                        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
                                        try {
                                            String line;
                                            while ((line = reader.readLine()) != null) {
                                                output.append(line).append("\n");
                                            }
                                        } finally {
                                            reader.close();
                                        }
                                        lines = output.toString();
                                        break;
                                    } catch (IllegalThreadStateException ignored) {
                                        try {
                                            Thread.sleep(timeUnit.toMillis(pause));
                                        } catch (InterruptedException exception) {
                                            Thread.currentThread().interrupt();
                                            throw new IllegalStateException(exception);
                                        }
                                    }
                                } while (--attempts > 0);
                                if (lines == null) {
                                    process.destroy();
                                    throw new IllegalStateException("Command for istat did not exit in time");
                                }
                                Matcher matcher = AIX_OWNER_PATTERN.matcher(lines);
                                if (matcher.find()) {
                                    return Integer.parseInt(matcher.group(1));
                                } else {
                                    throw new IllegalStateException("Unable to parse response from istat command: " + lines);
                                }
                            } catch (IOException exception) {
                                throw new IllegalStateException("Unable to execute istat command", exception);
                            }
                        }
                    }
                }
            }

            /**
             * 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(String processId) {
                    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();
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void chownFileToUser(File file, long userId) {
                    /* do nothing */
                }

                /**
                 * 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());
                                    }
                                } else {
                                    throw new Win32Exception(lastError);
                                }
                            }
                            int result = Kernel32.INSTANCE.WaitForSingleObject(mutex, 2000);
                            switch (result) {
                                case WinBase.WAIT_FAILED:
                                case WinError.WAIT_TIMEOUT:
                                    throw new Win32Exception(result);
                                default:
                                    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());
                                        }
                                    }
                            }
                        } finally {
                            securityAttributes = null;
                        }
                    } finally {
                        securityDescriptor = null;
                    }
                }

                /**
                 * A library for interacting with Windows.
                 */
                protected interface WindowsLibrary extends StdCallLibrary {

                    /**
                     * Indicates that a semaphore requires all access rights.
                     */
                    int SEMAPHORE_ALL_ACCESS = 0x1F0003;

                    /**
                     * 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.
                     */
                    @MaybeNull
                    @SuppressWarnings("checkstyle:methodname")
                    WinNT.HANDLE OpenSemaphoreW(int access, boolean inheritHandle, String name);

                    /**
                     * 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.
                     */
                    @MaybeNull
                    @SuppressWarnings("checkstyle:methodname")
                    WinNT.HANDLE CreateSemaphoreW(@MaybeNull WinBase.SECURITY_ATTRIBUTES securityAttributes,
                                                  long count,
                                                  long maximumCount,
                                                  String name);

                    /**
                     * 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, @MaybeNull Long previousCount);

                    /**
                     * 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.
                     */
                    @MaybeNull
                    @SuppressWarnings("checkstyle:methodname")
                    WinNT.HANDLE CreateMutex(SecurityAttributes attributes, boolean owner, String name);

                    /**
                     * 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);

                    /**
                     * 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);

                    /**
                     * 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.
                         */
                        @MaybeNull
                        public WinDef.DWORD length;

                        /**
                         * A pointer to the descriptor.
                         */
                        @MaybeNull
                        public Pointer securityDescriptor;

                        /**
                         * {@code true} if the attributes are inherited.
                         */
                        @SuppressWarnings("unused")
                        public boolean inherit;

                        @Override
                        protected List<String> getFieldOrder() {
                            return Arrays.asList("length", "securityDescriptor", "inherit");
                        }
                    }
                }

                /**
                 * A handle for an attachment which is represented by a pair of handles.
                 */
                protected static class AttachmentHandle implements Closeable {

                    /**
                     * The parent handle.
                     */
                    private final WinNT.HANDLE parent;

                    /**
                     * The child handle.
                     */
                    private final WinNT.HANDLE child;

                    /**
                     * 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 handle on which signals are to be sent.
                     *
                     * @return The handle on which signals are to be sent.
                     */
                    protected WinNT.HANDLE getHandle() {
                        return child;
                    }

                    /**
                     * {@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());
                        }
                    }
                }
            }
        }
    }
}
