// Modified version (see https://github.com/docker-java/docker-java/pull/697)
/**
* 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 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;
}
if (closed) {
return -1;
}
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;
}
}
}
}
}