package sun.nio.ch;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ProtocolFamily;
import java.net.ServerSocket;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.SocketTimeoutException;
import java.net.StandardSocketOptions;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.AlreadyBoundException;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.NotYetBoundException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import static java.net.StandardProtocolFamily.INET;
import static java.net.StandardProtocolFamily.INET6;
import static java.net.StandardProtocolFamily.UNIX;
import sun.net.NetHooks;
import sun.net.ext.ExtendedSocketOptions;
class ServerSocketChannelImpl
extends ServerSocketChannel
implements SelChImpl
{
private static final NativeDispatcher nd = new SocketDispatcher();
private final ProtocolFamily family;
private final FileDescriptor fd;
private final int fdVal;
private final ReentrantLock acceptLock = new ReentrantLock();
private final Object stateLock = new Object();
private static final int ST_INUSE = 0;
private static final int ST_CLOSING = 1;
private static final int ST_CLOSED = 2;
private int state;
private long thread;
private SocketAddress localAddress;
private boolean isReuseAddress;
private ServerSocket socket;
ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
this(sp, Net.isIPv6Available() ? INET6 : INET);
}
ServerSocketChannelImpl(SelectorProvider sp, ProtocolFamily family)
throws IOException
{
super(sp);
Objects.requireNonNull(family, "'family' is null");
if ((family != INET) && (family != INET6) && (family != UNIX)) {
throw new UnsupportedOperationException("Protocol family not supported");
}
if (family == INET6 && !Net.isIPv6Available()) {
throw new UnsupportedOperationException("IPv6 not available");
}
this.family = family;
if (family == UNIX) {
this.fd = UnixDomainSockets.socket();
} else {
this.fd = Net.serverSocket(family, true);
}
this.fdVal = IOUtil.fdVal(fd);
}
ServerSocketChannelImpl(SelectorProvider sp,
ProtocolFamily family,
FileDescriptor fd,
boolean bound)
throws IOException
{
super(sp);
if (family == UNIX) {
this.family = UNIX;
} else {
this.family = Net.isIPv6Available() ? INET6 : INET;
}
this.fd = fd;
this.fdVal = IOUtil.fdVal(fd);
if (bound) {
synchronized (stateLock) {
if (family == UNIX) {
localAddress = UnixDomainSockets.localAddress(fd);
} else {
localAddress = Net.localAddress(fd);
}
}
}
}
private boolean isNetSocket() {
return (family == INET) || (family == INET6);
}
boolean isUnixSocket() {
return (family == UNIX);
}
private void ensureOpen() throws ClosedChannelException {
if (!isOpen())
throw new ClosedChannelException();
}
@Override
public ServerSocket socket() {
synchronized (stateLock) {
if (socket == null) {
if (isNetSocket()) {
socket = ServerSocketAdaptor.create(this);
} else {
throw new UnsupportedOperationException("Not supported");
}
}
return socket;
}
}
@Override
public SocketAddress getLocalAddress() throws IOException {
synchronized (stateLock) {
ensureOpen();
if (isUnixSocket()) {
return UnixDomainSockets.getRevealedLocalAddress(localAddress);
} else {
return Net.getRevealedLocalAddress(localAddress);
}
}
}
@Override
public <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
throws IOException
{
Objects.requireNonNull(name);
if (!supportedOptions().contains(name))
throw new UnsupportedOperationException("'" + name + "' not supported");
if (!name.type().isInstance(value))
throw new IllegalArgumentException("Invalid value '" + value + "'");
synchronized (stateLock) {
ensureOpen();
if (isNetSocket()
&& name == StandardSocketOptions.SO_REUSEADDR
&& Net.useExclusiveBind()) {
isReuseAddress = (Boolean) value;
} else {
Net.setSocketOption(fd, Net.UNSPEC, name, value);
}
return this;
}
}
@Override
@SuppressWarnings("unchecked")
public <T> T getOption(SocketOption<T> name)
throws IOException
{
Objects.requireNonNull(name);
if (!supportedOptions().contains(name))
throw new UnsupportedOperationException("'" + name + "' not supported");
synchronized (stateLock) {
ensureOpen();
if (isNetSocket()
&& name == StandardSocketOptions.SO_REUSEADDR
&& Net.useExclusiveBind()) {
return (T) Boolean.valueOf(isReuseAddress);
} else {
return (T) Net.getSocketOption(fd, Net.UNSPEC, name);
}
}
}
private static class DefaultOptionsHolder {
static final Set<SocketOption<?>> defaultInetOptions = defaultInetOptions();
static final Set<SocketOption<?>> defaultUnixDomainOptions = defaultUnixDomainOptions();
private static Set<SocketOption<?>> defaultInetOptions() {
HashSet<SocketOption<?>> set = new HashSet<>();
set.add(StandardSocketOptions.SO_RCVBUF);
set.add(StandardSocketOptions.SO_REUSEADDR);
if (Net.isReusePortAvailable()) {
set.add(StandardSocketOptions.SO_REUSEPORT);
}
set.addAll(ExtendedSocketOptions.serverSocketOptions());
return Collections.unmodifiableSet(set);
}
private static Set<SocketOption<?>> defaultUnixDomainOptions() {
HashSet<SocketOption<?>> set = new HashSet<>();
set.add(StandardSocketOptions.SO_RCVBUF);
return Collections.unmodifiableSet(set);
}
}
@Override
public final Set<SocketOption<?>> supportedOptions() {
if (isUnixSocket()) {
return DefaultOptionsHolder.defaultUnixDomainOptions;
} else {
return DefaultOptionsHolder.defaultInetOptions;
}
}
@Override
public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
synchronized (stateLock) {
ensureOpen();
if (localAddress != null)
throw new AlreadyBoundException();
if (isUnixSocket()) {
localAddress = unixBind(local, backlog);
} else {
localAddress = netBind(local, backlog);
}
}
return this;
}
private SocketAddress unixBind(SocketAddress local, int backlog) throws IOException {
UnixDomainSockets.checkPermission();
if (local == null) {
boolean bound = false;
int attempts = 0;
while (attempts < 10 && !bound) {
try {
Path path = UnixDomainSockets.generateTempName().getPath();
UnixDomainSockets.bind(fd, path);
bound = true;
} catch (BindException e) { }
attempts++;
}
if (!bound)
throw new BindException("Could not bind to temporary name");
} else {
Path path = UnixDomainSockets.checkAddress(local).getPath();
UnixDomainSockets.bind(fd, path);
}
Net.listen(fd, backlog < 1 ? 50 : backlog);
return UnixDomainSockets.localAddress(fd);
}
private SocketAddress netBind(SocketAddress local, int backlog) throws IOException {
InetSocketAddress isa;
if (local == null) {
isa = new InetSocketAddress(Net.anyLocalAddress(family), 0);
} else {
isa = Net.checkAddress(local, family);
}
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkListen(isa.getPort());
NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
Net.bind(family, fd, isa.getAddress(), isa.getPort());
Net.listen(fd, backlog < 1 ? 50 : backlog);
return Net.localAddress(fd);
}
private void begin(boolean blocking) throws ClosedChannelException {
if (blocking)
begin();
synchronized (stateLock) {
ensureOpen();
if (localAddress == null)
throw new NotYetBoundException();
if (blocking)
thread = NativeThread.current();
}
}
private void end(boolean blocking, boolean completed)
throws AsynchronousCloseException
{
if (blocking) {
synchronized (stateLock) {
thread = 0;
if (state == ST_CLOSING) {
tryFinishClose();
}
}
end(completed);
}
}
@Override
public SocketChannel accept() throws IOException {
int n = 0;
FileDescriptor newfd = new FileDescriptor();
SocketAddress[] saa = new SocketAddress[1];
acceptLock.lock();
try {
boolean blocking = isBlocking();
try {
begin(blocking);
n = implAccept(this.fd, newfd, saa);
if (blocking) {
while (IOStatus.okayToRetry(n) && isOpen()) {
park(Net.POLLIN);
n = implAccept(this.fd, newfd, saa);
}
}
} finally {
end(blocking, n > 0);
assert IOStatus.check(n);
}
} finally {
acceptLock.unlock();
}
if (n > 0) {
return finishAccept(newfd, saa[0]);
} else {
return null;
}
}
private int implAccept(FileDescriptor fd, FileDescriptor newfd, SocketAddress[] saa)
throws IOException
{
if (isUnixSocket()) {
UnixDomainSockets.checkPermission();
String[] pa = new String[1];
int n = UnixDomainSockets.accept(fd, newfd, pa);
if (n > 0)
saa[0] = UnixDomainSocketAddress.of(pa[0]);
return n;
} else {
InetSocketAddress[] issa = new InetSocketAddress[1];
int n = Net.accept(fd, newfd, issa);
if (n > 0)
saa[0] = issa[0];
return n;
}
}
SocketChannel blockingAccept(long nanos) throws IOException {
int n = 0;
FileDescriptor newfd = new FileDescriptor();
SocketAddress[] saa = new SocketAddress[1];
acceptLock.lock();
try {
if (!isBlocking())
throw new IllegalBlockingModeException();
try {
begin(true);
lockedConfigureBlocking(false);
try {
long startNanos = System.nanoTime();
n = implAccept(fd, newfd, saa);
while (n == IOStatus.UNAVAILABLE && isOpen()) {
long remainingNanos = nanos - (System.nanoTime() - startNanos);
if (remainingNanos <= 0) {
throw new SocketTimeoutException("Accept timed out");
}
park(Net.POLLIN, remainingNanos);
n = implAccept(fd, newfd, saa);
}
} finally {
tryLockedConfigureBlocking(true);
}
} finally {
end(true, n > 0);
}
} finally {
acceptLock.unlock();
}
assert n > 0;
return finishAccept(newfd, saa[0]);
}
private SocketChannel finishAccept(FileDescriptor newfd, SocketAddress sa)
throws IOException
{
try {
IOUtil.configureBlocking(newfd, true);
if (isNetSocket()) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
InetSocketAddress isa = (InetSocketAddress) sa;
sm.checkAccept(isa.getAddress().getHostAddress(), isa.getPort());
}
}
return new SocketChannelImpl(provider(), family, newfd, sa);
} catch (Exception e) {
nd.close(newfd);
throw e;
}
}
@Override
protected void implConfigureBlocking(boolean block) throws IOException {
acceptLock.lock();
try {
lockedConfigureBlocking(block);
} finally {
acceptLock.unlock();
}
}
private void lockedConfigureBlocking(boolean block) throws IOException {
assert acceptLock.isHeldByCurrentThread();
synchronized (stateLock) {
ensureOpen();
IOUtil.configureBlocking(fd, block);
}
}
private boolean tryLockedConfigureBlocking(boolean block) throws IOException {
assert acceptLock.isHeldByCurrentThread();
synchronized (stateLock) {
if (isOpen()) {
IOUtil.configureBlocking(fd, block);
return true;
} else {
return false;
}
}
}
private boolean tryClose() throws IOException {
assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
if ((thread == 0) && !isRegistered()) {
state = ST_CLOSED;
nd.close(fd);
return true;
} else {
return false;
}
}
private void tryFinishClose() {
try {
tryClose();
} catch (IOException ignore) { }
}
private void implCloseBlockingMode() throws IOException {
synchronized (stateLock) {
assert state < ST_CLOSING;
state = ST_CLOSING;
if (!tryClose()) {
long th = thread;
if (th != 0) {
nd.preClose(fd);
NativeThread.signal(th);
}
}
}
}
private void implCloseNonBlockingMode() throws IOException {
synchronized (stateLock) {
assert state < ST_CLOSING;
state = ST_CLOSING;
}
acceptLock.lock();
acceptLock.unlock();
synchronized (stateLock) {
if (state == ST_CLOSING) {
tryClose();
}
}
}
@Override
protected void implCloseSelectableChannel() throws IOException {
assert !isOpen();
if (isBlocking()) {
implCloseBlockingMode();
} else {
implCloseNonBlockingMode();
}
}
@Override
public void kill() {
synchronized (stateLock) {
if (state == ST_CLOSING) {
tryFinishClose();
}
}
}
boolean isBound() {
synchronized (stateLock) {
return localAddress != null;
}
}
SocketAddress localAddress() {
synchronized (stateLock) {
return localAddress;
}
}
public boolean translateReadyOps(int ops, int initialOps, SelectionKeyImpl ski) {
int intOps = ski.nioInterestOps();
int oldOps = ski.nioReadyOps();
int newOps = initialOps;
if ((ops & Net.POLLNVAL) != 0) {
return false;
}
if ((ops & (Net.POLLERR | Net.POLLHUP)) != 0) {
newOps = intOps;
ski.nioReadyOps(newOps);
return (newOps & ~oldOps) != 0;
}
if (((ops & Net.POLLIN) != 0) &&
((intOps & SelectionKey.OP_ACCEPT) != 0))
newOps |= SelectionKey.OP_ACCEPT;
ski.nioReadyOps(newOps);
return (newOps & ~oldOps) != 0;
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl ski) {
return translateReadyOps(ops, ski.nioReadyOps(), ski);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl ski) {
return translateReadyOps(ops, 0, ski);
}
public int translateInterestOps(int ops) {
int newOps = 0;
if ((ops & SelectionKey.OP_ACCEPT) != 0)
newOps |= Net.POLLIN;
return newOps;
}
public FileDescriptor getFD() {
return fd;
}
public int getFDVal() {
return fdVal;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName());
sb.append('[');
if (!isOpen()) {
sb.append("closed");
} else {
synchronized (stateLock) {
SocketAddress addr = localAddress;
if (addr == null) {
sb.append("unbound");
} else if (isUnixSocket()) {
sb.append(UnixDomainSockets.getRevealedLocalAddressAsString(addr));
} else {
sb.append(Net.getRevealedLocalAddressAsString(addr));
}
}
}
sb.append(']');
return sb.toString();
}
}