8231187: SelectorProvider.inheritedChannel() returns TCP socket channel for Unix domain socket
authormichaelm
Tue, 24 Sep 2019 16:19:11 +0100
changeset 58295 7973073dd048
parent 58294 872465abbfe3
child 58296 7b534a5088d9
8231187: SelectorProvider.inheritedChannel() returns TCP socket channel for Unix domain socket Reviewed-by: alanb, chegar
src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorProvider.java
src/java.base/unix/classes/sun/nio/ch/InheritedChannel.java
src/java.base/unix/classes/sun/nio/ch/UnixDomainSocketChannelImpl.java
src/java.base/unix/native/libnio/ch/InheritedChannel.c
test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java
test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/Launcher.java
test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/UnixDomainChannelTest.java
test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/UnixDomainSocket.java
test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/libInheritedChannel.c
--- a/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorProvider.java	Tue Sep 24 17:08:19 2019 +0200
+++ b/src/java.base/macosx/classes/sun/nio/ch/KQueueSelectorProvider.java	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
 
 import java.io.IOException;
 import java.nio.channels.spi.AbstractSelector;
+import java.nio.channels.*;
 
 public class KQueueSelectorProvider
     extends SelectorProviderImpl
@@ -34,4 +35,8 @@
     public AbstractSelector openSelector() throws IOException {
         return new KQueueSelectorImpl(this);
     }
+
+    public Channel inheritedChannel() throws IOException {
+        return InheritedChannel.getChannel();
+    }
 }
--- a/src/java.base/unix/classes/sun/nio/ch/InheritedChannel.java	Tue Sep 24 17:08:19 2019 +0200
+++ b/src/java.base/unix/classes/sun/nio/ch/InheritedChannel.java	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -43,6 +43,12 @@
     private static final int SOCK_STREAM        = 1;
     private static final int SOCK_DGRAM         = 2;
 
+    // socket address type
+    private static final int AF_UNKNOWN         = -1;
+    private static final int AF_INET            = 1;
+    private static final int AF_INET6           = 2;
+    private static final int AF_UNIX            = 3;
+
     // oflag values when opening a file
     private static final int O_RDONLY           = 0;
     private static final int O_WRONLY           = 1;
@@ -89,6 +95,20 @@
         }
     }
 
+    public static class InheritedUnixChannelImpl extends UnixDomainSocketChannelImpl {
+
+        InheritedUnixChannelImpl(FileDescriptor fd)
+            throws IOException
+        {
+            super(fd);
+        }
+
+        protected void implCloseSelectableChannel() throws IOException {
+            super.implCloseChannel();
+            detachIOStreams();
+        }
+    }
+
     public static class InheritedServerSocketChannelImpl extends
         ServerSocketChannelImpl {
 
@@ -160,7 +180,6 @@
             return null;
         }
 
-
         // Next we create a FileDescriptor for the dup'ed file descriptor
         // Have to use reflection and also make assumption on how FD
         // is implemented.
@@ -182,6 +201,17 @@
 
         Channel c;
         if (st == SOCK_STREAM) {
+            int family = addressFamily(fdVal);
+            if (family == AF_UNKNOWN)
+                return null;
+            if (family == AF_UNIX) {
+                if (isConnected(fdVal)) {
+                    return new InheritedUnixChannelImpl(fd);
+                } else {
+                    // listener. unsupported.
+                    return null;
+                }
+            }
             InetAddress ia = peerAddress0(fdVal);
             if (ia == null) {
                c = new InheritedServerSocketChannelImpl(provider, fd);
@@ -232,9 +262,13 @@
     private static native int open0(String path, int oflag) throws IOException;
     private static native void close0(int fd) throws IOException;
     private static native int soType0(int fd);
+    private static native int addressFamily(int fd);
     private static native InetAddress peerAddress0(int fd);
     private static native int peerPort0(int fd);
 
+    // return true if socket is connected to a peer
+    private static native boolean isConnected(int fd);
+
     static {
         IOUtil.load();
         initIDs();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/unix/classes/sun/nio/ch/UnixDomainSocketChannelImpl.java	Tue Sep 24 16:19:11 2019 +0100
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.nio.ch;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetConnectedException;
+import java.nio.channels.spi.AbstractInterruptibleChannel;
+import java.util.Objects;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+class UnixDomainSocketChannelImpl
+    extends AbstractInterruptibleChannel
+    implements ByteChannel
+{
+    // Used to make native read and write calls
+    private static final NativeDispatcher nd = new SocketDispatcher();
+
+    // Our file descriptor object
+    private final FileDescriptor fd;
+    // Lock held by current reading or connecting thread
+    private final ReentrantLock readLock = new ReentrantLock();
+
+    // Lock held by current writing or connecting thread
+    private final ReentrantLock writeLock = new ReentrantLock();
+
+    // Lock for managing close state
+    private final Object stateLock = new Object();
+
+    // Channel state
+    private static final int ST_INUSE = 0;
+    private static final int ST_CLOSING = 1;
+    private static final int ST_CLOSED = 2;
+    private int state;
+
+    // IDs of native threads doing reads and writes, for signalling
+    private long readerThread;
+    private long writerThread;
+
+    UnixDomainSocketChannelImpl(FileDescriptor fd)
+        throws IOException
+    {
+        this.fd = fd;
+    }
+
+    /**
+     * Checks that the channel is open.
+     *
+     * @throws ClosedChannelException if channel is closed (or closing)
+     */
+    private void ensureOpen() throws ClosedChannelException {
+        if (!isOpen())
+            throw new ClosedChannelException();
+    }
+
+    /**
+     * Closes the socket if there are no I/O operations in progress
+     */
+    private boolean tryClose() throws IOException {
+        assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
+        if (readerThread == 0 && writerThread == 0) {
+            state = ST_CLOSED;
+            nd.close(fd);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Complete closure of pre-closed socket (release the file descriptor)
+     */
+    private void tryFinishClose() {
+        try {
+            tryClose();
+        } catch (IOException ignore) { }
+    }
+
+    /**
+     * Marks the beginning of a read operation
+     *
+     * @throws ClosedChannelException if the channel is closed
+     * @throws NotYetConnectedException if the channel is not yet connected
+     */
+    private void beginRead() throws ClosedChannelException {
+        // set hook for Thread.interrupt
+        begin();
+        synchronized (stateLock) {
+            ensureOpen();
+            readerThread = NativeThread.current();
+        }
+    }
+
+    /**
+     * Marks the end of a read operation that may have blocked.
+     *
+     * @throws AsynchronousCloseException if the channel was closed due to this
+     * thread being interrupted on a blocking read operation.
+     */
+    private void endRead(boolean completed)
+        throws AsynchronousCloseException
+    {
+        synchronized (stateLock) {
+            readerThread = 0;
+            if (state == ST_CLOSING) {
+                tryFinishClose();
+            }
+        }
+        end(completed);
+    }
+
+    @Override
+    public int read(ByteBuffer buf) throws IOException {
+        Objects.requireNonNull(buf);
+
+        readLock.lock();
+        try {
+            int n = 0;
+            try {
+                beginRead();
+                n = IOUtil.read(fd, buf, -1, nd);
+                while (IOStatus.okayToRetry(n) && isOpen()) {
+                    park(Net.POLLIN, 0L);
+                    n = IOUtil.read(fd, buf, -1, nd);
+                }
+            } finally {
+                endRead(n > 0);
+            }
+            return n;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Marks the beginning of a write operation that might block.
+     *
+     * @throws ClosedChannelException if the channel is closed
+     * @throws NotYetConnectedException if the channel is not yet connected
+     */
+    private void beginWrite() throws ClosedChannelException {
+        begin();
+        synchronized (stateLock) {
+            // set hook for Thread.interrupt
+            ensureOpen();
+            writerThread = NativeThread.current();
+        }
+    }
+
+    /**
+     * Marks the end of a write operation that may have blocked.
+     *
+     * @throws AsynchronousCloseException if the channel was closed due to this
+     * thread being interrupted on a blocking write operation.
+     */
+    private void endWrite(boolean completed)
+        throws AsynchronousCloseException
+    {
+        synchronized (stateLock) {
+            writerThread = 0;
+            if (state == ST_CLOSING) {
+                tryFinishClose();
+            }
+        }
+        end(completed);
+    }
+
+    void park(int event, long nanos) throws IOException {
+        long millis;
+        if (nanos <= 0) {
+            millis = -1;
+        } else {
+            millis = NANOSECONDS.toMillis(nanos);
+        }
+        Net.poll(fd, event, millis);
+    }
+
+    @Override
+    public int write(ByteBuffer buf) throws IOException {
+        Objects.requireNonNull(buf);
+
+        writeLock.lock();
+        try {
+            int n = 0;
+            try {
+                beginWrite();
+                n = IOUtil.write(fd, buf, -1, nd);
+                while (IOStatus.okayToRetry(n) && isOpen()) {
+                    park(Net.POLLOUT, 0L);
+                    n = IOUtil.write(fd, buf, -1, nd);
+                }
+            } finally {
+                endWrite(n > 0);
+            }
+            return n;
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    /**
+     * Closes this channel
+     *
+     * If there is an I/O operation in progress then the socket is pre-closed
+     * and the I/O threads signalled, in which case the final close is deferred
+     * until all I/O operations complete.
+     */
+    @Override
+    protected void implCloseChannel() throws IOException {
+        synchronized (stateLock) {
+            assert state == ST_INUSE;
+            state = ST_CLOSING;
+            if (!tryClose()) {
+                long reader = readerThread;
+                long writer = writerThread;
+                if (reader != 0 || writer != 0) {
+                    nd.preClose(fd);
+                    if (reader != 0)
+                        NativeThread.signal(reader);
+                    if (writer != 0)
+                        NativeThread.signal(writer);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSuperclass().getName());
+        sb.append('[');
+        if (!isOpen())
+            sb.append("closed");
+        sb.append(']');
+        return sb.toString();
+    }
+}
--- a/src/java.base/unix/native/libnio/ch/InheritedChannel.c	Tue Sep 24 17:08:19 2019 +0200
+++ b/src/java.base/unix/native/libnio/ch/InheritedChannel.c	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -82,6 +82,39 @@
 }
 
 JNIEXPORT jint JNICALL
+Java_sun_nio_ch_InheritedChannel_addressFamily(JNIEnv *env, jclass cla, jint fd)
+{
+    SOCKETADDRESS addr;
+    socklen_t addrlen = sizeof(addr);
+
+    if (getsockname(fd, (struct sockaddr *)&addr, &addrlen) < 0) {
+        return sun_nio_ch_InheritedChannel_AF_UNKNOWN;
+    }
+    if (addr.sa.sa_family == AF_INET) {
+        return sun_nio_ch_InheritedChannel_AF_INET;
+    }
+    if (addr.sa.sa_family == AF_INET6) {
+        return sun_nio_ch_InheritedChannel_AF_INET6;
+    }
+    if (addr.sa.sa_family == AF_UNIX) {
+        return sun_nio_ch_InheritedChannel_AF_UNIX;
+    }
+    return sun_nio_ch_InheritedChannel_AF_UNKNOWN;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_sun_nio_ch_InheritedChannel_isConnected(JNIEnv *env, jclass cla, jint fd)
+{
+    SOCKETADDRESS addr;
+    socklen_t addrlen = sizeof(addr);
+
+    if (getpeername(fd, (struct sockaddr *)&addr, &addrlen) < 0) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+JNIEXPORT jint JNICALL
 Java_sun_nio_ch_InheritedChannel_soType0(JNIEnv *env, jclass cla, jint fd)
 {
     int sotype;
--- a/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java	Tue Sep 24 17:08:19 2019 +0200
+++ b/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/InheritedChannelTest.java	Tue Sep 24 16:19:11 2019 +0100
@@ -25,7 +25,7 @@
  * @test
  * @bug 4673940 4930794 8211842
  * @summary Unit tests for inetd feature
- * @requires (os.family == "linux" | os.family == "solaris")
+ * @requires (os.family == "linux" | os.family == "solaris" | os.family == "mac")
  * @library /test/lib
  * @build jdk.test.lib.Utils
  *        jdk.test.lib.Asserts
@@ -33,7 +33,8 @@
  *        jdk.test.lib.JDKToolLauncher
  *        jdk.test.lib.Platform
  *        jdk.test.lib.process.*
- *        UnixSocketTest StateTest StateTestService EchoTest EchoService CloseTest Launcher Util
+ *        UnixSocketTest StateTest StateTestService EchoTest EchoService
+ *        UnixDomainChannelTest CloseTest Launcher Util
  * @run testng/othervm/native InheritedChannelTest
  * @key intermittent
  */
@@ -73,7 +74,8 @@
 
     @DataProvider
     public Object[][] testCases() {
-        return new Object[][]{
+        return new Object[][] {
+            { "UnixDomainChannelTest", List.of(UnixDomainChannelTest.class.getName())},
             { "UnixSocketTest", List.of(UnixSocketTest.class.getName())},
             { "StateTest", List.of(StateTest.class.getName()) },
             { "EchoTest",  List.of(EchoTest.class.getName())  },
@@ -83,6 +85,7 @@
             // Note that the system properties are arguments to StateTest and not options.
             // These system properties are passed to the launched service as options:
             // java [-options] class [args...]
+
             { "StateTest run with " + POLICY_PASS, List.of(StateTest.class.getName(),
                                                            "-Djava.security.manager",
                                                            "-Djava.security.policy="
@@ -97,7 +100,7 @@
         };
     }
 
-    @Test(dataProvider = "testCases")
+    @Test(dataProvider = "testCases", timeOut=30000)
     public void test(String desc, List<String> opts) throws Throwable {
         String pathVar = Platform.sharedLibraryPathVariableName();
         System.out.println(pathVar + "=" + libraryPath);
--- a/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/Launcher.java	Tue Sep 24 17:08:19 2019 +0200
+++ b/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/Launcher.java	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,4 @@
 /*
- * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -75,6 +74,15 @@
         return socks[1];
     }
 
+    /**
+     * Launch specified class with an AF_UNIX socket created externally, and one String arg to child VM
+     */
+    public static void launchWithUnixDomainSocket(String className, UnixDomainSocket socket, String arg) throws IOException {
+        String[] args = new String[1];
+        args[0] = arg;
+        launch(className, null, args, socket.fd());
+    }
+
     /*
      * Launch 'java' with specified class with the specified arguments (may be null).
      * The launched process will inherit a connected TCP socket. The remote endpoint
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/UnixDomainChannelTest.java	Tue Sep 24 16:19:11 2019 +0100
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.nio.channels.*;
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+
+/*
+ * Make sure that System.inheritedChannel returns null when given a UNIX domain socket
+ */
+
+public class UnixDomainChannelTest {
+
+    public static class Child {
+        public static void main(String[] args) throws Exception {
+            // we just want to make sure that System.inheritedChannel either
+            // returns a connected channel, or null if it is given a listener
+            Channel channel = System.inheritedChannel();
+            String result = channel == null ? "N" : "Y";
+            if (args[0].equals("test1") || args[0].equals("test2")) {
+                // socket is writeable
+                ByteChannel bc = (ByteChannel)channel;
+                ByteBuffer buf = ByteBuffer.wrap(result.getBytes(ISO_8859_1));
+                bc.write(buf);
+            } else { // test3
+                // in this case the socket is a listener
+                // we can't write to it. So, use UnixDatagramSocket
+                // to accept a writeable socket
+                UnixDomainSocket listener = new UnixDomainSocket(0); // fd 0
+                UnixDomainSocket sock = listener.accept();
+                sock.write((int)result.charAt(0));
+            }
+        }
+    }
+
+    static boolean passed = true;
+
+    public static void main(String args[]) throws Exception {
+        test1();
+        test2();
+        test3();
+        if (!passed)
+            throw new RuntimeException();
+    }
+
+    private static void closeAll(UnixDomainSocket... sockets) {
+        for (UnixDomainSocket sock : sockets) {
+            sock.close();
+        }
+    }
+
+    // Test with a named connected socket
+    private static void test1() throws Exception {
+        UnixDomainSocket listener = new UnixDomainSocket();
+        listener.bind("foo.socket");
+        UnixDomainSocket sock1 = new UnixDomainSocket();
+        sock1.connect("foo.socket");
+        UnixDomainSocket sock2 = listener.accept();
+
+        Launcher.launchWithUnixDomainSocket("UnixDomainChannelTest$Child", sock2, "test1");
+        int c = sock1.read();
+        if (c != 'Y') {
+            System.err.printf("test1: failed %d d\n", c );
+            passed = false;
+        }
+        closeAll(listener, sock1, sock2);
+    }
+
+    // Test with unnamed socketpair
+    private static void test2() throws Exception {
+        UnixDomainSocket[] pair = UnixDomainSocket.socketpair();
+        System.out.println("test2: launching child");
+        Launcher.launchWithUnixDomainSocket("UnixDomainChannelTest$Child", pair[0], "test2");
+        if (pair[1].read() != 'Y') {
+            System.err.println("test2: failed");
+            passed = false;
+        }
+        closeAll(pair[0], pair[1]);
+    }
+
+    // Test with a named listener
+    private static void test3() throws Exception {
+        UnixDomainSocket listener = new UnixDomainSocket();
+        listener.bind("foo.socket");
+        UnixDomainSocket sock1 = new UnixDomainSocket();
+        System.out.println("test3: launching child");
+        Launcher.launchWithUnixDomainSocket("UnixDomainChannelTest$Child", listener, "test3");
+        sock1.connect("foo.socket");
+        if (sock1.read() != 'N') {
+            System.err.println("test3: failed");
+            passed = false;
+        }
+        closeAll(listener, sock1);
+    }
+
+}
--- a/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/UnixDomainSocket.java	Tue Sep 24 17:08:19 2019 +0200
+++ b/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/UnixDomainSocket.java	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,21 +37,44 @@
     }
 
     private final int fd;
+    private volatile String name;
+
+    public UnixDomainSocket() throws IOException {
+        this.fd = create();
+    }
+
+    public void bind(String name) throws IOException {
+        bind0(fd, name);
+        this.name = name;
+    }
+
+    public UnixDomainSocket accept() throws IOException {
+        int newsock = accept0(fd);
+        return new UnixDomainSocket(newsock);
+    }
 
     public UnixDomainSocket(int fd) {
         this.fd = fd;
     }
 
+    public void connect(String dest) throws IOException {
+        connect0(fd, dest);
+    }
+
     public int read() throws IOException {
         return read0(fd);
     }
 
+    public String name() {
+        return name;
+    }
+
     public void write(int w) throws IOException {
         write0(fd, w);
     }
 
     public void close() {
-        close0(fd);
+        close0(fd, name); // close0 will unlink name if non-null
     }
 
     public int fd() {
@@ -62,11 +85,16 @@
         return "UnixDomainSocket: fd=" + Integer.toString(fd);
     }
 
+    private static native int create() throws IOException;
+    private static native void bind0(int fd, String name) throws IOException;
+    private static native int accept0(int fd) throws IOException;
+    private static native int connect0(int fd, String name) throws IOException;
+
     /* read and write bytes with UNIX domain sockets */
 
     private static native int read0(int fd) throws IOException;
     private static native void write0(int fd, int w) throws IOException;
-    private static native void close0(int fd);
+    private static native void close0(int fd, String name);
     private static native void init();
     public static native UnixDomainSocket[] socketpair();
 }
--- a/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/libInheritedChannel.c	Tue Sep 24 17:08:19 2019 +0200
+++ b/test/jdk/java/nio/channels/spi/SelectorProvider/inheritedChannel/libInheritedChannel.c	Tue Sep 24 16:19:11 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,8 +26,10 @@
  */
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/un.h>
 #include <unistd.h>
 #include <dirent.h>
 #include <sys/stat.h>
@@ -147,19 +149,18 @@
 
     /*
      * We need to close all file descriptors except for serviceFd. To
-     * get the list of open file descriptos we read through /proc/self/fd
+     * get the list of open file descriptos we read through /proc/self/fd (/dev/fd)
      * but to open this requires a file descriptor. We could use a specific
      * file descriptor and fdopendir but Linux doesn't seem to support
      * fdopendir. Instead we use opendir and make an assumption on the
      * file descriptor that is used (by opening & closing a file).
      */
-    thisFd = open("/dev/null", O_RDONLY);
+    thisFd = open("/dev/fd", O_RDONLY);
     if (thisFd < 0) {
         _exit(-1);
     }
-    close(thisFd);
 
-    if ((dp = opendir("/proc/self/fd")) == NULL) {
+    if ((dp = fdopendir(thisFd)) == NULL) {
         _exit(-1);
     }
 
@@ -216,6 +217,65 @@
     return result;
 }
 
+JNIEXPORT jint JNICALL Java_UnixDomainSocket_create
+  (JNIEnv *env, jclass cls)
+{
+    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+    if (sock == -1) {
+        ThrowException(env, "java/io/IOException", "socket create error");
+    }
+    return sock;
+}
+
+JNIEXPORT void JNICALL Java_UnixDomainSocket_bind0
+  (JNIEnv *env, jclass cls, jint sock, jstring name)
+{
+    struct sockaddr_un addr;
+    const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
+    int ret = -1;
+    unlink(nameUtf);
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, nameUtf, strlen(nameUtf));
+    ret = bind(sock, (const struct sockaddr*)&addr, sizeof(addr));
+    if (ret == -1) {
+        ThrowException(env, "java/io/IOException", "socket bind error");
+    }
+    ret = listen(sock, 5);
+    if (ret == -1) {
+        ThrowException(env, "java/io/IOException", "socket bind error");
+    }
+    (*env)->ReleaseStringUTFChars(env, name, nameUtf);
+}
+
+JNIEXPORT jint JNICALL Java_UnixDomainSocket_accept0
+  (JNIEnv *env, jclass cls, jint sock)
+{
+    struct sockaddr_storage addr;
+    socklen_t len = sizeof(addr);
+    int ret = accept(sock, (struct sockaddr *)&addr, &len);
+    if (ret == -1)
+        ThrowException(env, "java/io/IOException", "socket accept error");
+    return ret;
+}
+
+JNIEXPORT void JNICALL Java_UnixDomainSocket_connect0
+  (JNIEnv *env, jclass cls, jint fd, jstring name)
+{
+    struct sockaddr_un addr;
+    const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
+    int ret = -1;
+    memset(&addr, 0, sizeof(addr));
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, nameUtf, strlen(nameUtf));
+    ret = connect(fd, (const struct sockaddr*)&addr, sizeof(addr));
+    if (ret == -1) {
+        ThrowException(env, "java/io/IOException", "socket connect error");
+    }
+    (*env)->ReleaseStringUTFChars(env, name, nameUtf);
+}
+
+
 JNIEXPORT jint JNICALL Java_UnixDomainSocket_read0
   (JNIEnv *env, jclass cls, jint fd)
 {
@@ -243,7 +303,12 @@
 }
 
 JNIEXPORT void JNICALL Java_UnixDomainSocket_close0
-  (JNIEnv *env, jclass cls, jint fd)
+  (JNIEnv *env, jclass cls, jint fd, jstring name)
 {
     close(fd);
+    if (name != NULL) {
+        const char *nameUtf = (*env)->GetStringUTFChars(env, name, NULL);
+        unlink(nameUtf);
+        (*env)->ReleaseStringUTFChars(env, name, nameUtf);
+    }
 }