richie
7 years ago
12 changed files with 2650 additions and 0 deletions
@ -0,0 +1,92 @@ |
|||||||
|
package com.fr.third.net.bytebuddy.agent; |
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A Java program that attaches a Java agent to an external process. |
||||||
|
*/ |
||||||
|
public class Attacher { |
||||||
|
|
||||||
|
/** |
||||||
|
* Base for access to a reflective member to make the code more readable. |
||||||
|
*/ |
||||||
|
private static final Object STATIC_MEMBER = null; |
||||||
|
|
||||||
|
/** |
||||||
|
* The name of the {@code attach} method of the {@code VirtualMachine} class. |
||||||
|
*/ |
||||||
|
private static final String ATTACH_METHOD_NAME = "attach"; |
||||||
|
|
||||||
|
/** |
||||||
|
* The name of the {@code loadAgent} method of the {@code VirtualMachine} class. |
||||||
|
*/ |
||||||
|
private static final String LOAD_AGENT_METHOD_NAME = "loadAgent"; |
||||||
|
|
||||||
|
/** |
||||||
|
* The name of the {@code detach} method of the {@code VirtualMachine} class. |
||||||
|
*/ |
||||||
|
private static final String DETACH_METHOD_NAME = "detach"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Runs the attacher as a Java application. |
||||||
|
* |
||||||
|
* @param args A list containing the fully qualified name of the virtual machine type, |
||||||
|
* the process id, the fully qualified name of the Java agent jar followed by |
||||||
|
* an empty string if the argument to the agent is {@code null} or any number |
||||||
|
* of strings where the first argument is proceeded by any single character |
||||||
|
* which is stripped off. |
||||||
|
*/ |
||||||
|
public static void main(String[] args) { |
||||||
|
try { |
||||||
|
String argument; |
||||||
|
if (args.length < 4 || args[3].isEmpty()) { |
||||||
|
argument = null; |
||||||
|
} else { |
||||||
|
StringBuilder stringBuilder = new StringBuilder(args[3].substring(1)); |
||||||
|
for (int index = 4; index < args.length; index++) { |
||||||
|
stringBuilder.append(' ').append(args[index]); |
||||||
|
} |
||||||
|
argument = stringBuilder.toString(); |
||||||
|
} |
||||||
|
install(Class.forName(args[0]), args[1], args[2], argument); |
||||||
|
} catch (Exception ignored) { |
||||||
|
System.exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Installs a Java agent on a target VM. |
||||||
|
* |
||||||
|
* @param virtualMachineType The virtual machine type to use for the external attachment. |
||||||
|
* @param processId The id of the process being target of the external attachment. |
||||||
|
* @param agent The Java agent to attach. |
||||||
|
* @param argument The argument to provide or {@code null} if no argument is provided. |
||||||
|
* @throws NoSuchMethodException If the virtual machine type does not define an expected method. |
||||||
|
* @throws InvocationTargetException If the virtual machine type raises an error. |
||||||
|
* @throws IllegalAccessException If a method of the virtual machine type cannot be accessed. |
||||||
|
*/ |
||||||
|
protected static void install(Class<?> virtualMachineType, |
||||||
|
String processId, |
||||||
|
String agent, |
||||||
|
String argument) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { |
||||||
|
Object virtualMachineInstance = virtualMachineType |
||||||
|
.getMethod(ATTACH_METHOD_NAME, String.class) |
||||||
|
.invoke(STATIC_MEMBER, processId); |
||||||
|
try { |
||||||
|
virtualMachineType |
||||||
|
.getMethod(LOAD_AGENT_METHOD_NAME, String.class, String.class) |
||||||
|
.invoke(virtualMachineInstance, agent, argument); |
||||||
|
} finally { |
||||||
|
virtualMachineType |
||||||
|
.getMethod(DETACH_METHOD_NAME) |
||||||
|
.invoke(virtualMachineInstance); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* The attacher provides only {@code static} utility methods and should not be instantiated. |
||||||
|
*/ |
||||||
|
private Attacher() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,71 @@ |
|||||||
|
package com.fr.third.net.bytebuddy.agent; |
||||||
|
|
||||||
|
import java.lang.instrument.Instrumentation; |
||||||
|
|
||||||
|
/** |
||||||
|
* An installer class which defined the hook-in methods that are required by the Java agent specification. |
||||||
|
*/ |
||||||
|
public class Installer { |
||||||
|
|
||||||
|
/** |
||||||
|
* A field for carrying the {@link java.lang.instrument.Instrumentation} that was loaded by the Byte Buddy |
||||||
|
* agent. Note that this field must never be accessed directly as the agent is injected into the VM's |
||||||
|
* system class loader. This way, the field of this class might be {@code null} even after the installation |
||||||
|
* of the Byte Buddy agent as this class might be loaded by a different class loader than the system class
|
||||||
|
* loader. |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unused") |
||||||
|
private static volatile Instrumentation instrumentation; |
||||||
|
|
||||||
|
/** |
||||||
|
* The installer provides only {@code static} hook-in methods and should not be instantiated. |
||||||
|
*/ |
||||||
|
private Installer() { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Returns the instrumentation that was loaded by the Byte Buddy agent. When a security manager is active, |
||||||
|
* the {@link RuntimePermission} for {@code getInstrumentation} is required by the caller. |
||||||
|
* </p> |
||||||
|
* <p> |
||||||
|
* <b>Important</b>: This method must only be invoked via the {@link ClassLoader#getSystemClassLoader()} where any |
||||||
|
* Java agent is loaded. It is possible that two versions of this class exist for different class loaders. |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @return The instrumentation instance of the Byte Buddy agent. |
||||||
|
*/ |
||||||
|
public static Instrumentation getInstrumentation() { |
||||||
|
SecurityManager securityManager = System.getSecurityManager(); |
||||||
|
if (securityManager != null) { |
||||||
|
securityManager.checkPermission(new RuntimePermission("getInstrumentation")); |
||||||
|
} |
||||||
|
Instrumentation instrumentation = Installer.instrumentation; |
||||||
|
if (instrumentation == null) { |
||||||
|
throw new IllegalStateException("The Byte Buddy agent is not loaded or this method is not called via the system class loader"); |
||||||
|
} |
||||||
|
return instrumentation; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Allows the installation of this agent via a command line argument. |
||||||
|
* |
||||||
|
* @param agentArguments The unused agent arguments. |
||||||
|
* @param instrumentation The instrumentation instance. |
||||||
|
*/ |
||||||
|
public static void premain(String agentArguments, Instrumentation instrumentation) { |
||||||
|
Installer.instrumentation = instrumentation; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Allows the installation of this agent via the Attach API. |
||||||
|
* |
||||||
|
* @param agentArguments The unused agent arguments. |
||||||
|
* @param instrumentation The instrumentation instance. |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unused") |
||||||
|
public static void agentmain(String agentArguments, Instrumentation instrumentation) { |
||||||
|
Installer.instrumentation = instrumentation; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,336 @@ |
|||||||
|
package com.fr.third.net.bytebuddy.agent; |
||||||
|
|
||||||
|
import com.fr.third.org.newsclub.net.unix.AFUNIXSocket; |
||||||
|
import com.fr.third.org.newsclub.net.unix.AFUNIXSocketAddress; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.charset.Charset; |
||||||
|
import java.util.Locale; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* <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 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. |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unused") |
||||||
|
void loadAgent(String jarFile, String argument) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Detaches this virtual machine representation. |
||||||
|
* |
||||||
|
* @throws IOException If an I/O exception occurs. |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unused") |
||||||
|
void detach() throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A virtual machine implementation for a HotSpot VM or any compatible VM. |
||||||
|
*/ |
||||||
|
abstract class ForHotSpot implements VirtualMachine { |
||||||
|
|
||||||
|
/** |
||||||
|
* The UTF-8 charset. |
||||||
|
*/ |
||||||
|
private static final Charset UTF_8 = Charset.forName("UTF-8"); |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 = "="; |
||||||
|
|
||||||
|
/** |
||||||
|
* A blank line argument. |
||||||
|
*/ |
||||||
|
private static final byte[] BLANK = new byte[]{0}; |
||||||
|
|
||||||
|
/** |
||||||
|
* The target process's id. |
||||||
|
*/ |
||||||
|
protected final String processId; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new HotSpot-compatible VM implementation. |
||||||
|
* |
||||||
|
* @param processId The target process's id. |
||||||
|
*/ |
||||||
|
protected ForHotSpot(String processId) { |
||||||
|
this.processId = processId; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void loadAgent(String jarFile, String argument) throws IOException { |
||||||
|
connect(); |
||||||
|
write(PROTOCOL_VERSION.getBytes(UTF_8)); |
||||||
|
write(BLANK); |
||||||
|
write(LOAD_COMMAND.getBytes(UTF_8)); |
||||||
|
write(BLANK); |
||||||
|
write(INSTRUMENT_COMMAND.getBytes(UTF_8)); |
||||||
|
write(BLANK); |
||||||
|
write(Boolean.FALSE.toString().getBytes(UTF_8)); |
||||||
|
write(BLANK); |
||||||
|
write((argument == null |
||||||
|
? jarFile |
||||||
|
: jarFile + ARGUMENT_DELIMITER + argument).getBytes(UTF_8)); |
||||||
|
write(BLANK); |
||||||
|
byte[] buffer = new byte[1]; |
||||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||||
|
int length; |
||||||
|
while ((length = read(buffer)) != -1) { |
||||||
|
if (length > 0) { |
||||||
|
if (buffer[0] == 10) { |
||||||
|
break; |
||||||
|
} |
||||||
|
stringBuilder.append((char) buffer[0]); |
||||||
|
} |
||||||
|
} |
||||||
|
switch (Integer.parseInt(stringBuilder.toString())) { |
||||||
|
case 0: |
||||||
|
return; |
||||||
|
case 101: |
||||||
|
throw new IOException("Protocol mismatch with target VM"); |
||||||
|
default: |
||||||
|
buffer = new byte[1024]; |
||||||
|
stringBuilder = new StringBuilder(); |
||||||
|
while ((length = read(buffer)) != -1) { |
||||||
|
stringBuilder.append(new String(buffer, 0, length, UTF_8)); |
||||||
|
} |
||||||
|
throw new IllegalStateException(stringBuilder.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Connects to the target VM. |
||||||
|
* |
||||||
|
* @throws IOException If an I/O exception occurs. |
||||||
|
*/ |
||||||
|
protected abstract void connect() throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Reads from the communication channel. |
||||||
|
* |
||||||
|
* @param buffer The buffer to read into. |
||||||
|
* @return The amount of bytes read. |
||||||
|
* @throws IOException If an I/O exception occurs. |
||||||
|
*/ |
||||||
|
protected abstract int read(byte[] buffer) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Writes to the communication channel. |
||||||
|
* |
||||||
|
* @param buffer The buffer to write from. |
||||||
|
* @throws IOException If an I/O exception occurs. |
||||||
|
*/ |
||||||
|
protected abstract void write(byte[] buffer) throws IOException; |
||||||
|
|
||||||
|
/** |
||||||
|
* A virtual machine implementation for a HotSpot VM running on Unix. |
||||||
|
*/ |
||||||
|
public static class OnUnix extends ForHotSpot { |
||||||
|
|
||||||
|
/** |
||||||
|
* The default amount of attempts to connect. |
||||||
|
*/ |
||||||
|
private static final int DEFAULT_ATTEMPTS = 10; |
||||||
|
|
||||||
|
/** |
||||||
|
* The default pause between two attempts. |
||||||
|
*/ |
||||||
|
private static final long DEFAULT_PAUSE = 200; |
||||||
|
|
||||||
|
/** |
||||||
|
* The default socket timeout. |
||||||
|
*/ |
||||||
|
private static final long DEFAULT_TIMEOUT = 5000; |
||||||
|
|
||||||
|
/** |
||||||
|
* The temporary directory on Unix systems. |
||||||
|
*/ |
||||||
|
private static final String TEMPORARY_DIRECTORY = "/tmp"; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 Unix socket to use for communication. The containing object is supposed to be an instance |
||||||
|
* of {@link AFUNIXSocket} which is however not set to avoid eager loading |
||||||
|
*/ |
||||||
|
private final Object socket; |
||||||
|
|
||||||
|
/** |
||||||
|
* The number of attempts to connect. |
||||||
|
*/ |
||||||
|
private final int attempts; |
||||||
|
|
||||||
|
/** |
||||||
|
* The time to pause between attempts. |
||||||
|
*/ |
||||||
|
private final long pause; |
||||||
|
|
||||||
|
/** |
||||||
|
* The socket timeout. |
||||||
|
*/ |
||||||
|
private final long timeout; |
||||||
|
|
||||||
|
/** |
||||||
|
* The time unit of the pause time. |
||||||
|
*/ |
||||||
|
private final TimeUnit timeUnit; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new VM implementation for a HotSpot VM running on Unix. |
||||||
|
* |
||||||
|
* @param processId The process id of the target VM. |
||||||
|
* @param socket The Unix socket to use for communication. |
||||||
|
* @param attempts The number of attempts to connect. |
||||||
|
* @param pause The pause time between two VMs. |
||||||
|
* @param timeout The socket timeout. |
||||||
|
* @param timeUnit The time unit of the pause time. |
||||||
|
*/ |
||||||
|
public OnUnix(String processId, Object socket, int attempts, long pause, long timeout, TimeUnit timeUnit) { |
||||||
|
super(processId); |
||||||
|
this.socket = socket; |
||||||
|
this.attempts = attempts; |
||||||
|
this.pause = pause; |
||||||
|
this.timeout = timeout; |
||||||
|
this.timeUnit = timeUnit; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Asserts the availability of this virtual machine implementation. If the Unix socket library is missing or |
||||||
|
* if this VM does not support Unix socket communication, a {@link Throwable} is thrown. |
||||||
|
* |
||||||
|
* @return This virtual machine type. |
||||||
|
* @throws Throwable If this VM does not support POSIX sockets or is not running on a HotSpot VM. |
||||||
|
*/ |
||||||
|
public static Class<?> assertAvailability() throws Throwable { |
||||||
|
if (!AFUNIXSocket.isSupported()) { |
||||||
|
throw new IllegalStateException("POSIX sockets are not supported on the current system"); |
||||||
|
} else if (!System.getProperty("java.vm.name").toLowerCase(Locale.US).contains("hotspot")) { |
||||||
|
throw new IllegalStateException("Cannot apply attachment on non-Hotspot compatible VM"); |
||||||
|
} else { |
||||||
|
return OnUnix.class; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attaches to the supplied VM process. |
||||||
|
* |
||||||
|
* @param processId The process id of the target VM. |
||||||
|
* @return An appropriate virtual machine implementation. |
||||||
|
* @throws IOException If an I/O exception occurs. |
||||||
|
*/ |
||||||
|
public static VirtualMachine attach(String processId) throws IOException { |
||||||
|
return new OnUnix(processId, AFUNIXSocket.newInstance(), DEFAULT_ATTEMPTS, DEFAULT_PAUSE, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void connect() throws IOException { |
||||||
|
File socketFile = new File(TEMPORARY_DIRECTORY, SOCKET_FILE_PREFIX + processId); |
||||||
|
if (!socketFile.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(TEMPORARY_DIRECTORY, target); |
||||||
|
if (!attachFile.createNewFile() && !attachFile.isFile()) { |
||||||
|
throw new IllegalStateException("Could not create attach file: " + attachFile); |
||||||
|
} |
||||||
|
} |
||||||
|
try { |
||||||
|
// The HotSpot attachment API attempts to send the signal to all children of a process
|
||||||
|
Process process = Runtime.getRuntime().exec("kill -3 " + processId); |
||||||
|
int attempts = this.attempts; |
||||||
|
boolean killed = false; |
||||||
|
do { |
||||||
|
try { |
||||||
|
if (process.exitValue() != 0) { |
||||||
|
throw new IllegalStateException("Error while sending signal to target VM: " + processId); |
||||||
|
} |
||||||
|
killed = true; |
||||||
|
break; |
||||||
|
} catch (IllegalThreadStateException ignored) { |
||||||
|
attempts -= 1; |
||||||
|
Thread.sleep(timeUnit.toMillis(pause)); |
||||||
|
} |
||||||
|
} while (attempts > 0); |
||||||
|
if (!killed) { |
||||||
|
throw new IllegalStateException("Target VM did not respond to signal: " + processId); |
||||||
|
} |
||||||
|
attempts = this.attempts; |
||||||
|
while (attempts-- > 0 && !socketFile.exists()) { |
||||||
|
Thread.sleep(timeUnit.toMillis(pause)); |
||||||
|
} |
||||||
|
if (!socketFile.exists()) { |
||||||
|
throw new IllegalStateException("Target VM did not respond: " + processId); |
||||||
|
} |
||||||
|
} catch (InterruptedException exception) { |
||||||
|
throw new IllegalStateException("Interrupted during wait for process", exception); |
||||||
|
} finally { |
||||||
|
if (!attachFile.delete()) { |
||||||
|
attachFile.deleteOnExit(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
((AFUNIXSocket) socket).setSoTimeout((int) timeUnit.toMillis(timeout)); |
||||||
|
((AFUNIXSocket) socket).connect(new AFUNIXSocketAddress(socketFile)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int read(byte[] buffer) throws IOException { |
||||||
|
return ((AFUNIXSocket) this.socket).getInputStream().read(buffer); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(byte[] buffer) throws IOException { |
||||||
|
((AFUNIXSocket) this.socket).getOutputStream().write(buffer); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void detach() throws IOException { |
||||||
|
((AFUNIXSocket) this.socket).close(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
/** |
||||||
|
* The Byte Buddy agent allows the redefinition of classes at runtime. |
||||||
|
*/ |
||||||
|
package com.fr.third.net.bytebuddy.agent; |
@ -0,0 +1,141 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.net.ServerSocket; |
||||||
|
import java.net.Socket; |
||||||
|
import java.net.SocketAddress; |
||||||
|
import java.net.SocketException; |
||||||
|
|
||||||
|
/** |
||||||
|
* The server part of an AF_UNIX domain socket. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
public class AFUNIXServerSocket extends ServerSocket { |
||||||
|
private final AFUNIXSocketImpl implementation; |
||||||
|
private AFUNIXSocketAddress boundEndpoint = null; |
||||||
|
|
||||||
|
private final Thread shutdownThread = new Thread() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
try { |
||||||
|
if (boundEndpoint != null) { |
||||||
|
NativeUnixSocket.unlink(boundEndpoint.getSocketFile()); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
// ignore
|
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
protected AFUNIXServerSocket() throws IOException { |
||||||
|
super(); |
||||||
|
this.implementation = new AFUNIXSocketImpl(); |
||||||
|
NativeUnixSocket.initServerImpl(this, implementation); |
||||||
|
|
||||||
|
Runtime.getRuntime().addShutdownHook(shutdownThread); |
||||||
|
NativeUnixSocket.setCreatedServer(this); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new, unbound AF_UNIX {@link ServerSocket}. |
||||||
|
* |
||||||
|
* @return The new, unbound {@link AFUNIXServerSocket}. |
||||||
|
*/ |
||||||
|
public static AFUNIXServerSocket newInstance() throws IOException { |
||||||
|
AFUNIXServerSocket instance = new AFUNIXServerSocket(); |
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a new AF_UNIX {@link ServerSocket} that is bound to the given |
||||||
|
* {@link AFUNIXSocketAddress}. |
||||||
|
* |
||||||
|
* @return The new, unbound {@link AFUNIXServerSocket}. |
||||||
|
*/ |
||||||
|
public static AFUNIXServerSocket bindOn(final AFUNIXSocketAddress addr) throws IOException { |
||||||
|
AFUNIXServerSocket socket = newInstance(); |
||||||
|
socket.bind(addr); |
||||||
|
return socket; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void bind(SocketAddress endpoint, int backlog) throws IOException { |
||||||
|
if (isClosed()) { |
||||||
|
throw new SocketException("Socket is closed"); |
||||||
|
} |
||||||
|
if (isBound()) { |
||||||
|
throw new SocketException("Already bound"); |
||||||
|
} |
||||||
|
if (!(endpoint instanceof AFUNIXSocketAddress)) { |
||||||
|
throw new IOException("Can only bind to endpoints of type " |
||||||
|
+ AFUNIXSocketAddress.class.getName()); |
||||||
|
} |
||||||
|
implementation.bind(backlog, endpoint); |
||||||
|
boundEndpoint = (AFUNIXSocketAddress) endpoint; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isBound() { |
||||||
|
return boundEndpoint != null; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Socket accept() throws IOException { |
||||||
|
if (isClosed()) { |
||||||
|
throw new SocketException("Socket is closed"); |
||||||
|
} |
||||||
|
AFUNIXSocket as = AFUNIXSocket.newInstance(); |
||||||
|
implementation.accept(as.impl); |
||||||
|
as.addr = boundEndpoint; |
||||||
|
NativeUnixSocket.setConnected(as); |
||||||
|
return as; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
if (!isBound()) { |
||||||
|
return "AFUNIXServerSocket[unbound]"; |
||||||
|
} |
||||||
|
return "AFUNIXServerSocket[" + boundEndpoint.getSocketFile() + "]"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws IOException { |
||||||
|
if (isClosed()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
super.close(); |
||||||
|
implementation.close(); |
||||||
|
if (boundEndpoint != null) { |
||||||
|
NativeUnixSocket.unlink(boundEndpoint.getSocketFile()); |
||||||
|
} |
||||||
|
try { |
||||||
|
Runtime.getRuntime().removeShutdownHook(shutdownThread); |
||||||
|
} catch (IllegalStateException e) { |
||||||
|
// ignore
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static boolean isSupported() { |
||||||
|
return NativeUnixSocket.isLoaded(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.net.Socket; |
||||||
|
import java.net.SocketAddress; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implementation of an AF_UNIX domain socket. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
public class AFUNIXSocket extends Socket { |
||||||
|
protected AFUNIXSocketImpl impl; |
||||||
|
AFUNIXSocketAddress addr; |
||||||
|
|
||||||
|
private AFUNIXSocket(final AFUNIXSocketImpl impl) throws IOException { |
||||||
|
super(impl); |
||||||
|
try { |
||||||
|
NativeUnixSocket.setCreated(this); |
||||||
|
} catch (UnsatisfiedLinkError e) { |
||||||
|
e.printStackTrace(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new, unbound {@link AFUNIXSocket}. |
||||||
|
* |
||||||
|
* This "default" implementation is a bit "lenient" with respect to the specification. |
||||||
|
* |
||||||
|
* In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and |
||||||
|
* {@link Socket#setTcpNoDelay(boolean)}. |
||||||
|
* |
||||||
|
* @return A new, unbound socket. |
||||||
|
*/ |
||||||
|
public static AFUNIXSocket newInstance() throws IOException { |
||||||
|
final AFUNIXSocketImpl impl = new AFUNIXSocketImpl.Lenient(); |
||||||
|
AFUNIXSocket instance = new AFUNIXSocket(impl); |
||||||
|
instance.impl = impl; |
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new, unbound, "strict" {@link AFUNIXSocket}. |
||||||
|
* |
||||||
|
* This call uses an implementation that tries to be closer to the specification than |
||||||
|
* {@link #newInstance()}, at least for some cases. |
||||||
|
* |
||||||
|
* @return A new, unbound socket. |
||||||
|
*/ |
||||||
|
public static AFUNIXSocket newStrictInstance() throws IOException { |
||||||
|
final AFUNIXSocketImpl impl = new AFUNIXSocketImpl(); |
||||||
|
AFUNIXSocket instance = new AFUNIXSocket(impl); |
||||||
|
instance.impl = impl; |
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link AFUNIXSocket} and connects it to the given {@link AFUNIXSocketAddress}. |
||||||
|
* |
||||||
|
* @param addr The address to connect to. |
||||||
|
* @return A new, connected socket. |
||||||
|
*/ |
||||||
|
public static AFUNIXSocket connectTo(AFUNIXSocketAddress addr) throws IOException { |
||||||
|
AFUNIXSocket socket = newInstance(); |
||||||
|
socket.connect(addr); |
||||||
|
return socket; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Binds this {@link AFUNIXSocket} to the given bindpoint. Only bindpoints of the type |
||||||
|
* {@link AFUNIXSocketAddress} are supported. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void bind(SocketAddress bindpoint) throws IOException { |
||||||
|
super.bind(bindpoint); |
||||||
|
this.addr = (AFUNIXSocketAddress) bindpoint; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connect(SocketAddress endpoint) throws IOException { |
||||||
|
connect(endpoint, 0); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void connect(SocketAddress endpoint, int timeout) throws IOException { |
||||||
|
if (!(endpoint instanceof AFUNIXSocketAddress)) { |
||||||
|
throw new IOException("Can only connect to endpoints of type " |
||||||
|
+ AFUNIXSocketAddress.class.getName()); |
||||||
|
} |
||||||
|
impl.connect(endpoint, timeout); |
||||||
|
this.addr = (AFUNIXSocketAddress) endpoint; |
||||||
|
NativeUnixSocket.setConnected(this); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
if (isConnected()) { |
||||||
|
return "AFUNIXSocket[fd=" + impl.getFD() + ";path=" + addr.getSocketFile() + "]"; |
||||||
|
} |
||||||
|
return "AFUNIXSocket[unconnected]"; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns <code>true</code> iff {@link AFUNIXSocket}s are supported by the current Java VM. |
||||||
|
* |
||||||
|
* To support {@link AFUNIXSocket}s, a custom JNI library must be loaded that is supplied with |
||||||
|
* <em>junixsocket</em>. |
||||||
|
* |
||||||
|
* @return {@code true} iff supported. |
||||||
|
*/ |
||||||
|
public static boolean isSupported() { |
||||||
|
return NativeUnixSocket.isLoaded(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,76 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.net.InetSocketAddress; |
||||||
|
|
||||||
|
/** |
||||||
|
* Describes an {@link InetSocketAddress} that actually uses AF_UNIX sockets instead of AF_INET. |
||||||
|
* |
||||||
|
* The ability to specify a port number is not specified by AF_UNIX sockets, but we need it |
||||||
|
* sometimes, for example for RMI-over-AF_UNIX. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
public class AFUNIXSocketAddress extends InetSocketAddress { |
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L; |
||||||
|
private final String socketFile; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the |
||||||
|
* given file. |
||||||
|
* |
||||||
|
* @param socketFile The socket to connect to. |
||||||
|
*/ |
||||||
|
public AFUNIXSocketAddress(final File socketFile) throws IOException { |
||||||
|
this(socketFile, 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the |
||||||
|
* given file, assigning the given port to it. |
||||||
|
* |
||||||
|
* @param socketFile The socket to connect to. |
||||||
|
* @param port The port associated with this socket, or {@code 0} when no port should be assigned. |
||||||
|
*/ |
||||||
|
public AFUNIXSocketAddress(final File socketFile, int port) throws IOException { |
||||||
|
super(0); |
||||||
|
if (port != 0) { |
||||||
|
NativeUnixSocket.setPort1(this, port); |
||||||
|
} |
||||||
|
this.socketFile = socketFile.getCanonicalPath(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the (canonical) file path for this {@link AFUNIXSocketAddress}. |
||||||
|
* |
||||||
|
* @return The file path. |
||||||
|
*/ |
||||||
|
public String getSocketFile() { |
||||||
|
return socketFile; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return getClass().getName() + "[host=" + getHostName() + ";port=" + getPort() + ";file=" |
||||||
|
+ socketFile + "]"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.net.SocketException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Something went wrong with the communication to a Unix socket. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
public class AFUNIXSocketException extends SocketException { |
||||||
|
private static final long serialVersionUID = 1L; |
||||||
|
private final String socketFile; |
||||||
|
|
||||||
|
public AFUNIXSocketException(String reason) { |
||||||
|
this(reason, (String) null); |
||||||
|
} |
||||||
|
|
||||||
|
public AFUNIXSocketException(String reason, final Throwable cause) { |
||||||
|
this(reason, (String) null); |
||||||
|
initCause(cause); |
||||||
|
} |
||||||
|
|
||||||
|
public AFUNIXSocketException(String reason, final String socketFile) { |
||||||
|
super(reason); |
||||||
|
this.socketFile = socketFile; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
if (socketFile == null) { |
||||||
|
return super.toString(); |
||||||
|
} else { |
||||||
|
return super.toString() + " (socket: " + socketFile + ")"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,411 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.io.FileDescriptor; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.OutputStream; |
||||||
|
import java.net.InetAddress; |
||||||
|
import java.net.Socket; |
||||||
|
import java.net.SocketAddress; |
||||||
|
import java.net.SocketException; |
||||||
|
import java.net.SocketImpl; |
||||||
|
import java.net.SocketOptions; |
||||||
|
|
||||||
|
/** |
||||||
|
* The Java-part of the {@link AFUNIXSocket} implementation. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
class AFUNIXSocketImpl extends SocketImpl { |
||||||
|
private static final int SHUT_RD = 0; |
||||||
|
private static final int SHUT_WR = 1; |
||||||
|
private static final int SHUT_RD_WR = 2; |
||||||
|
|
||||||
|
private String socketFile; |
||||||
|
private boolean closed = false; |
||||||
|
private boolean bound = false; |
||||||
|
private boolean connected = false; |
||||||
|
|
||||||
|
private boolean closedInputStream = false; |
||||||
|
private boolean closedOutputStream = false; |
||||||
|
|
||||||
|
private final AFUNIXInputStream in = new AFUNIXInputStream(); |
||||||
|
private final AFUNIXOutputStream out = new AFUNIXOutputStream(); |
||||||
|
|
||||||
|
AFUNIXSocketImpl() { |
||||||
|
super(); |
||||||
|
this.fd = new FileDescriptor(); |
||||||
|
} |
||||||
|
|
||||||
|
FileDescriptor getFD() { |
||||||
|
return fd; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void accept(SocketImpl socket) throws IOException { |
||||||
|
final AFUNIXSocketImpl si = (AFUNIXSocketImpl) socket; |
||||||
|
NativeUnixSocket.accept(socketFile, fd, si.fd); |
||||||
|
si.socketFile = socketFile; |
||||||
|
si.connected = true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int available() throws IOException { |
||||||
|
return NativeUnixSocket.available(fd); |
||||||
|
} |
||||||
|
|
||||||
|
protected void bind(SocketAddress addr) throws IOException { |
||||||
|
bind(0, addr); |
||||||
|
} |
||||||
|
|
||||||
|
protected void bind(int backlog, SocketAddress addr) throws IOException { |
||||||
|
if (!(addr instanceof AFUNIXSocketAddress)) { |
||||||
|
throw new SocketException("Cannot bind to this type of address: " + addr.getClass()); |
||||||
|
} |
||||||
|
final AFUNIXSocketAddress socketAddress = (AFUNIXSocketAddress) addr; |
||||||
|
socketFile = socketAddress.getSocketFile(); |
||||||
|
NativeUnixSocket.bind(socketFile, fd, backlog); |
||||||
|
bound = true; |
||||||
|
this.localport = socketAddress.getPort(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("hiding") |
||||||
|
protected void bind(InetAddress host, int port) throws IOException { |
||||||
|
throw new SocketException("Cannot bind to this type of address: " + InetAddress.class); |
||||||
|
} |
||||||
|
|
||||||
|
private void checkClose() throws IOException { |
||||||
|
if (closedInputStream && closedOutputStream) { |
||||||
|
// close();
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected synchronized void close() throws IOException { |
||||||
|
if (closed) { |
||||||
|
return; |
||||||
|
} |
||||||
|
closed = true; |
||||||
|
if (fd.valid()) { |
||||||
|
NativeUnixSocket.shutdown(fd, SHUT_RD_WR); |
||||||
|
NativeUnixSocket.close(fd); |
||||||
|
} |
||||||
|
if (bound) { |
||||||
|
NativeUnixSocket.unlink(socketFile); |
||||||
|
} |
||||||
|
connected = false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("hiding") |
||||||
|
protected void connect(String host, int port) throws IOException { |
||||||
|
throw new SocketException("Cannot bind to this type of address: " + InetAddress.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@SuppressWarnings("hiding") |
||||||
|
protected void connect(InetAddress address, int port) throws IOException { |
||||||
|
throw new SocketException("Cannot bind to this type of address: " + InetAddress.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void connect(SocketAddress addr, int timeout) throws IOException { |
||||||
|
if (!(addr instanceof AFUNIXSocketAddress)) { |
||||||
|
throw new SocketException("Cannot bind to this type of address: " + addr.getClass()); |
||||||
|
} |
||||||
|
final AFUNIXSocketAddress socketAddress = (AFUNIXSocketAddress) addr; |
||||||
|
socketFile = socketAddress.getSocketFile(); |
||||||
|
NativeUnixSocket.connect(socketFile, fd); |
||||||
|
this.address = socketAddress.getAddress(); |
||||||
|
this.port = socketAddress.getPort(); |
||||||
|
this.localport = 0; |
||||||
|
this.connected = true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void create(boolean stream) throws IOException { |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected InputStream getInputStream() throws IOException { |
||||||
|
if (!connected && !bound) { |
||||||
|
throw new IOException("Not connected/not bound"); |
||||||
|
} |
||||||
|
return in; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected OutputStream getOutputStream() throws IOException { |
||||||
|
if (!connected && !bound) { |
||||||
|
throw new IOException("Not connected/not bound"); |
||||||
|
} |
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void listen(int backlog) throws IOException { |
||||||
|
NativeUnixSocket.listen(fd, backlog); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void sendUrgentData(int data) throws IOException { |
||||||
|
NativeUnixSocket.write(fd, new byte[] {(byte) (data & 0xFF)}, 0, 1); |
||||||
|
} |
||||||
|
|
||||||
|
private final class AFUNIXInputStream extends InputStream { |
||||||
|
private boolean streamClosed = false; |
||||||
|
|
||||||
|
@Override |
||||||
|
public int read(byte[] buf, int off, int len) throws IOException { |
||||||
|
if (streamClosed) { |
||||||
|
throw new IOException("This InputStream has already been closed."); |
||||||
|
} |
||||||
|
if (len == 0) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
int maxRead = buf.length - off; |
||||||
|
if (len > maxRead) { |
||||||
|
len = maxRead; |
||||||
|
} |
||||||
|
try { |
||||||
|
return NativeUnixSocket.read(fd, buf, off, len); |
||||||
|
} catch (final IOException e) { |
||||||
|
throw (IOException) new IOException(e.getMessage() + " at " |
||||||
|
+ AFUNIXSocketImpl.this.toString()).initCause(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int read() throws IOException { |
||||||
|
final byte[] buf1 = new byte[1]; |
||||||
|
final int numRead = read(buf1, 0, 1); |
||||||
|
if (numRead <= 0) { |
||||||
|
return -1; |
||||||
|
} else { |
||||||
|
return buf1[0] & 0xFF; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws IOException { |
||||||
|
if (streamClosed) { |
||||||
|
return; |
||||||
|
} |
||||||
|
streamClosed = true; |
||||||
|
if (fd.valid()) { |
||||||
|
NativeUnixSocket.shutdown(fd, SHUT_RD); |
||||||
|
} |
||||||
|
|
||||||
|
closedInputStream = true; |
||||||
|
checkClose(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int available() throws IOException { |
||||||
|
final int av = NativeUnixSocket.available(fd); |
||||||
|
return av; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final class AFUNIXOutputStream extends OutputStream { |
||||||
|
private boolean streamClosed = false; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(int oneByte) throws IOException { |
||||||
|
final byte[] buf1 = new byte[] {(byte) oneByte}; |
||||||
|
write(buf1, 0, 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void write(byte[] buf, int off, int len) throws IOException { |
||||||
|
if (streamClosed) { |
||||||
|
throw new AFUNIXSocketException("This OutputStream has already been closed."); |
||||||
|
} |
||||||
|
if (len > buf.length - off) { |
||||||
|
throw new IndexOutOfBoundsException(); |
||||||
|
} |
||||||
|
try { |
||||||
|
while (len > 0 && !Thread.interrupted()) { |
||||||
|
final int written = NativeUnixSocket.write(fd, buf, off, len); |
||||||
|
if (written == -1) { |
||||||
|
throw new IOException("Unspecific error while writing"); |
||||||
|
} |
||||||
|
len -= written; |
||||||
|
off += written; |
||||||
|
} |
||||||
|
} catch (final IOException e) { |
||||||
|
throw (IOException) new IOException(e.getMessage() + " at " |
||||||
|
+ AFUNIXSocketImpl.this.toString()).initCause(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws IOException { |
||||||
|
if (streamClosed) { |
||||||
|
return; |
||||||
|
} |
||||||
|
streamClosed = true; |
||||||
|
if (fd.valid()) { |
||||||
|
NativeUnixSocket.shutdown(fd, SHUT_WR); |
||||||
|
} |
||||||
|
closedOutputStream = true; |
||||||
|
checkClose(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return super.toString() + "[fd=" + fd + "; file=" + this.socketFile + "; connected=" |
||||||
|
+ connected + "; bound=" + bound + "]"; |
||||||
|
} |
||||||
|
|
||||||
|
private static int expectInteger(Object value) throws SocketException { |
||||||
|
try { |
||||||
|
return (Integer) value; |
||||||
|
} catch (final ClassCastException e) { |
||||||
|
throw new AFUNIXSocketException("Unsupported value: " + value, e); |
||||||
|
} catch (final NullPointerException e) { |
||||||
|
throw new AFUNIXSocketException("Value must not be null", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static int expectBoolean(Object value) throws SocketException { |
||||||
|
try { |
||||||
|
return ((Boolean) value).booleanValue() ? 1 : 0; |
||||||
|
} catch (final ClassCastException e) { |
||||||
|
throw new AFUNIXSocketException("Unsupported value: " + value, e); |
||||||
|
} catch (final NullPointerException e) { |
||||||
|
throw new AFUNIXSocketException("Value must not be null", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getOption(int optID) throws SocketException { |
||||||
|
try { |
||||||
|
switch (optID) { |
||||||
|
case SocketOptions.SO_KEEPALIVE: |
||||||
|
case SocketOptions.TCP_NODELAY: |
||||||
|
return NativeUnixSocket.getSocketOptionInt(fd, optID) != 0 ? true : false; |
||||||
|
case SocketOptions.SO_LINGER: |
||||||
|
case SocketOptions.SO_TIMEOUT: |
||||||
|
case SocketOptions.SO_RCVBUF: |
||||||
|
case SocketOptions.SO_SNDBUF: |
||||||
|
return NativeUnixSocket.getSocketOptionInt(fd, optID); |
||||||
|
default: |
||||||
|
throw new AFUNIXSocketException("Unsupported option: " + optID); |
||||||
|
} |
||||||
|
} catch (final AFUNIXSocketException e) { |
||||||
|
throw e; |
||||||
|
} catch (final Exception e) { |
||||||
|
throw new AFUNIXSocketException("Error while getting option", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setOption(int optID, Object value) throws SocketException { |
||||||
|
try { |
||||||
|
switch (optID) { |
||||||
|
case SocketOptions.SO_LINGER: |
||||||
|
|
||||||
|
if (value instanceof Boolean) { |
||||||
|
final boolean b = (Boolean) value; |
||||||
|
if (b) { |
||||||
|
throw new SocketException("Only accepting Boolean.FALSE here"); |
||||||
|
} |
||||||
|
NativeUnixSocket.setSocketOptionInt(fd, optID, -1); |
||||||
|
return; |
||||||
|
} |
||||||
|
NativeUnixSocket.setSocketOptionInt(fd, optID, expectInteger(value)); |
||||||
|
return; |
||||||
|
case SocketOptions.SO_RCVBUF: |
||||||
|
case SocketOptions.SO_SNDBUF: |
||||||
|
case SocketOptions.SO_TIMEOUT: |
||||||
|
NativeUnixSocket.setSocketOptionInt(fd, optID, expectInteger(value)); |
||||||
|
return; |
||||||
|
case SocketOptions.SO_KEEPALIVE: |
||||||
|
case SocketOptions.TCP_NODELAY: |
||||||
|
NativeUnixSocket.setSocketOptionInt(fd, optID, expectBoolean(value)); |
||||||
|
return; |
||||||
|
default: |
||||||
|
throw new AFUNIXSocketException("Unsupported option: " + optID); |
||||||
|
} |
||||||
|
} catch (final AFUNIXSocketException e) { |
||||||
|
throw e; |
||||||
|
} catch (final Exception e) { |
||||||
|
throw new AFUNIXSocketException("Error while setting option", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void shutdownInput() throws IOException { |
||||||
|
if (!closed && fd.valid()) { |
||||||
|
NativeUnixSocket.shutdown(fd, SHUT_RD); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void shutdownOutput() throws IOException { |
||||||
|
if (!closed && fd.valid()) { |
||||||
|
NativeUnixSocket.shutdown(fd, SHUT_WR); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes the behavior to be somewhat lenient with respect to the specification. |
||||||
|
* |
||||||
|
* In particular, we ignore calls to {@link Socket#getTcpNoDelay()} and |
||||||
|
* {@link Socket#setTcpNoDelay(boolean)}. |
||||||
|
*/ |
||||||
|
static class Lenient extends AFUNIXSocketImpl { |
||||||
|
Lenient() { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setOption(int optID, Object value) throws SocketException { |
||||||
|
try { |
||||||
|
super.setOption(optID, value); |
||||||
|
} catch (SocketException e) { |
||||||
|
switch (optID) { |
||||||
|
case SocketOptions.TCP_NODELAY: |
||||||
|
return; |
||||||
|
default: |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object getOption(int optID) throws SocketException { |
||||||
|
try { |
||||||
|
return super.getOption(optID); |
||||||
|
} catch (SocketException e) { |
||||||
|
switch (optID) { |
||||||
|
case SocketOptions.TCP_NODELAY: |
||||||
|
case SocketOptions.SO_KEEPALIVE: |
||||||
|
return false; |
||||||
|
default: |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,131 @@ |
|||||||
|
/** |
||||||
|
* junixsocket |
||||||
|
* |
||||||
|
* Copyright (c) 2009,2014 Christian Kohlschütter |
||||||
|
* |
||||||
|
* The author licenses this file to You 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 com.fr.third.org.newsclub.net.unix; |
||||||
|
|
||||||
|
import java.io.FileDescriptor; |
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Field; |
||||||
|
import java.net.InetSocketAddress; |
||||||
|
|
||||||
|
/** |
||||||
|
* JNI connector to native JNI C code. |
||||||
|
* |
||||||
|
* @author Christian Kohlschütter |
||||||
|
*/ |
||||||
|
final class NativeUnixSocket { |
||||||
|
private static boolean loaded = false; |
||||||
|
|
||||||
|
static { |
||||||
|
try { |
||||||
|
Class.forName("org.newsclub.net.unix.NarSystem").getMethod("loadLibrary").invoke(null); |
||||||
|
} catch (ClassNotFoundException e) { |
||||||
|
throw new IllegalStateException( |
||||||
|
"Could not find NarSystem class.\n\n*** ECLIPSE USERS ***\nIf you're running from " |
||||||
|
+ "within Eclipse, please try closing the \"junixsocket-native-common\" " |
||||||
|
+ "project\n", e); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new IllegalStateException(e); |
||||||
|
} |
||||||
|
loaded = true; |
||||||
|
} |
||||||
|
|
||||||
|
static boolean isLoaded() { |
||||||
|
return loaded; |
||||||
|
} |
||||||
|
|
||||||
|
static void checkSupported() { |
||||||
|
} |
||||||
|
|
||||||
|
static native void bind(final String socketFile, final FileDescriptor fd, final int backlog) |
||||||
|
throws IOException; |
||||||
|
|
||||||
|
static native void listen(final FileDescriptor fd, final int backlog) throws IOException; |
||||||
|
|
||||||
|
static native void accept(final String socketFile, final FileDescriptor fdServer, |
||||||
|
final FileDescriptor fd) throws IOException; |
||||||
|
|
||||||
|
static native void connect(final String socketFile, final FileDescriptor fd) throws IOException; |
||||||
|
|
||||||
|
static native int read(final FileDescriptor fd, byte[] buf, int off, int len) throws IOException; |
||||||
|
|
||||||
|
static native int write(final FileDescriptor fd, byte[] buf, int off, int len) throws IOException; |
||||||
|
|
||||||
|
static native void close(final FileDescriptor fd) throws IOException; |
||||||
|
|
||||||
|
static native void shutdown(final FileDescriptor fd, int mode) throws IOException; |
||||||
|
|
||||||
|
static native int getSocketOptionInt(final FileDescriptor fd, int optionId) throws IOException; |
||||||
|
|
||||||
|
static native void setSocketOptionInt(final FileDescriptor fd, int optionId, int value) |
||||||
|
throws IOException; |
||||||
|
|
||||||
|
static native void unlink(final String socketFile) throws IOException; |
||||||
|
|
||||||
|
static native int available(final FileDescriptor fd) throws IOException; |
||||||
|
|
||||||
|
static native void initServerImpl(final AFUNIXServerSocket serverSocket, |
||||||
|
final AFUNIXSocketImpl impl); |
||||||
|
|
||||||
|
static native void setCreated(final AFUNIXSocket socket); |
||||||
|
|
||||||
|
static native void setConnected(final AFUNIXSocket socket); |
||||||
|
|
||||||
|
static native void setBound(final AFUNIXSocket socket); |
||||||
|
|
||||||
|
static native void setCreatedServer(final AFUNIXServerSocket socket); |
||||||
|
|
||||||
|
static native void setBoundServer(final AFUNIXServerSocket socket); |
||||||
|
|
||||||
|
static native void setPort(final AFUNIXSocketAddress addr, int port); |
||||||
|
|
||||||
|
static void setPort1(AFUNIXSocketAddress addr, int port) throws AFUNIXSocketException { |
||||||
|
if (port < 0) { |
||||||
|
throw new IllegalArgumentException("port out of range:" + port); |
||||||
|
} |
||||||
|
|
||||||
|
boolean setOk = false; |
||||||
|
try { |
||||||
|
final Field holderField = InetSocketAddress.class.getDeclaredField("holder"); |
||||||
|
if (holderField != null) { |
||||||
|
holderField.setAccessible(true); |
||||||
|
|
||||||
|
final Object holder = holderField.get(addr); |
||||||
|
if (holder != null) { |
||||||
|
final Field portField = holder.getClass().getDeclaredField("port"); |
||||||
|
if (portField != null) { |
||||||
|
portField.setAccessible(true); |
||||||
|
portField.set(holder, port); |
||||||
|
setOk = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
setPort(addr, port); |
||||||
|
} |
||||||
|
} catch (final RuntimeException e) { |
||||||
|
throw e; |
||||||
|
} catch (final Exception e) { |
||||||
|
if (e instanceof AFUNIXSocketException) { |
||||||
|
throw (AFUNIXSocketException) e; |
||||||
|
} |
||||||
|
throw new AFUNIXSocketException("Could not set port", e); |
||||||
|
} |
||||||
|
if (!setOk) { |
||||||
|
throw new AFUNIXSocketException("Could not set port"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue