From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Timur Sultanov Date: Sun, 12 Jun 2022 15:58:40 -0600 Subject: [PATCH] Add serenity-specific modules to java.base and jdk.attach It would be nice to re-direct the build to the same files *BSD use, but for now we've got our own copy Co-Authored-By: Andrew Kaster --- .../DefaultAsynchronousChannelProvider.java | 47 ++ .../sun/nio/ch/DefaultSelectorProvider.java | 54 ++ .../SerenityAsynchronousChannelProvider.java | 91 +++ .../classes/sun/nio/ch/SerenityPollPort.java | 538 ++++++++++++++++++ .../sun/nio/fs/DefaultFileSystemProvider.java | 53 ++ .../classes/sun/nio/fs/SerenityFileStore.java | 105 ++++ .../sun/nio/fs/SerenityFileSystem.java | 94 +++ .../nio/fs/SerenityFileSystemProvider.java | 52 ++ .../sun/nio/fs/SerenityNativeDispatcher.java | 49 ++ .../serenity/native/libnet/serenity_close.c | 458 +++++++++++++++ .../sun/tools/attach/AttachProviderImpl.java | 82 +++ .../sun/tools/attach/VirtualMachineImpl.java | 326 +++++++++++ .../native/libattach/VirtualMachineImpl.c | 328 +++++++++++ 13 files changed, 2277 insertions(+) create mode 100644 src/java.base/serenity/classes/sun/nio/ch/DefaultAsynchronousChannelProvider.java create mode 100644 src/java.base/serenity/classes/sun/nio/ch/DefaultSelectorProvider.java create mode 100644 src/java.base/serenity/classes/sun/nio/ch/SerenityAsynchronousChannelProvider.java create mode 100644 src/java.base/serenity/classes/sun/nio/ch/SerenityPollPort.java create mode 100644 src/java.base/serenity/classes/sun/nio/fs/DefaultFileSystemProvider.java create mode 100644 src/java.base/serenity/classes/sun/nio/fs/SerenityFileStore.java create mode 100644 src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystem.java create mode 100644 src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystemProvider.java create mode 100644 src/java.base/serenity/classes/sun/nio/fs/SerenityNativeDispatcher.java create mode 100644 src/java.base/serenity/native/libnet/serenity_close.c create mode 100644 src/jdk.attach/serenity/classes/sun/tools/attach/AttachProviderImpl.java create mode 100644 src/jdk.attach/serenity/classes/sun/tools/attach/VirtualMachineImpl.java create mode 100644 src/jdk.attach/serenity/native/libattach/VirtualMachineImpl.c diff --git a/src/java.base/serenity/classes/sun/nio/ch/DefaultAsynchronousChannelProvider.java b/src/java.base/serenity/classes/sun/nio/ch/DefaultAsynchronousChannelProvider.java new file mode 100644 index 000000000..7a7bfe089 --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/ch/DefaultAsynchronousChannelProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, 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.nio.channels.spi.AsynchronousChannelProvider; + +/** + * Creates this platform's default AsynchronousChannelProvider + */ + +public class DefaultAsynchronousChannelProvider { + + /** + * Prevent instantiation. + */ + private DefaultAsynchronousChannelProvider() { } + + /** + * Returns the default AsynchronousChannelProvider. + */ + public static AsynchronousChannelProvider create() { + return new SerenityAsynchronousChannelProvider(); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/ch/DefaultSelectorProvider.java b/src/java.base/serenity/classes/sun/nio/ch/DefaultSelectorProvider.java new file mode 100644 index 000000000..86d3ade19 --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/ch/DefaultSelectorProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, 2021, 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.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Creates this platform's default SelectorProvider + */ + +@SuppressWarnings("removal") +public class DefaultSelectorProvider { + private static final SelectorProviderImpl INSTANCE; + static { + PrivilegedAction pa = PollSelectorProvider::new; + INSTANCE = AccessController.doPrivileged(pa); + } + + /** + * Prevent instantiation. + */ + private DefaultSelectorProvider() { } + + /** + * Returns the default SelectorProvider implementation. + */ + public static SelectorProviderImpl get() { + return INSTANCE; + } +} \ No newline at end of file diff --git a/src/java.base/serenity/classes/sun/nio/ch/SerenityAsynchronousChannelProvider.java b/src/java.base/serenity/classes/sun/nio/ch/SerenityAsynchronousChannelProvider.java new file mode 100644 index 000000000..2daa2cca4 --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/ch/SerenityAsynchronousChannelProvider.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 SAP SE. 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.nio.channels.*; +import java.nio.channels.spi.AsynchronousChannelProvider; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import java.io.IOException; + +public class SerenityAsynchronousChannelProvider + extends AsynchronousChannelProvider +{ + private static volatile SerenityPollPort defaultPort; + + private SerenityPollPort defaultEventPort() throws IOException { + if (defaultPort == null) { + synchronized (SerenityAsynchronousChannelProvider.class) { + if (defaultPort == null) { + defaultPort = new SerenityPollPort(this, ThreadPool.getDefault()).start(); + } + } + } + return defaultPort; + } + + public SerenityAsynchronousChannelProvider() { + } + + @Override + public AsynchronousChannelGroup openAsynchronousChannelGroup(int nThreads, ThreadFactory factory) + throws IOException + { + return new SerenityPollPort(this, ThreadPool.create(nThreads, factory)).start(); + } + + @Override + public AsynchronousChannelGroup openAsynchronousChannelGroup(ExecutorService executor, int initialSize) + throws IOException + { + return new SerenityPollPort(this, ThreadPool.wrap(executor, initialSize)).start(); + } + + private Port toPort(AsynchronousChannelGroup group) throws IOException { + if (group == null) { + return defaultEventPort(); + } else { + if (!(group instanceof SerenityPollPort)) + throw new IllegalChannelGroupException(); + return (Port)group; + } + } + + @Override + public AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(AsynchronousChannelGroup group) + throws IOException + { + return new UnixAsynchronousServerSocketChannelImpl(toPort(group)); + } + + @Override + public AsynchronousSocketChannel openAsynchronousSocketChannel(AsynchronousChannelGroup group) + throws IOException + { + return new UnixAsynchronousSocketChannelImpl(toPort(group)); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/ch/SerenityPollPort.java b/src/java.base/serenity/classes/sun/nio/ch/SerenityPollPort.java new file mode 100644 index 000000000..0894d1814 --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/ch/SerenityPollPort.java @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012 SAP SE. 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.nio.channels.spi.AsynchronousChannelProvider; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; +import jdk.internal.misc.Unsafe; + +/** + * AsynchronousChannelGroup implementation based on the AIX pollset framework. + */ +final class SerenityPollPort + extends Port +{ + private static final Unsafe unsafe = Unsafe.getUnsafe(); + + static { + IOUtil.load(); + init(); + } + + /** + * struct pollfd { + * int fd; + * short events; + * short revents; + * } + */ + private static final int SIZEOF_POLLFD = eventSize(); + private static final int OFFSETOF_EVENTS = eventsOffset(); + private static final int OFFSETOF_REVENTS = reventsOffset(); + private static final int OFFSETOF_FD = fdOffset(); + + // opcodes + private static final int PS_ADD = 0x0; + private static final int PS_MOD = 0x1; + private static final int PS_DELETE = 0x2; + + // maximum number of events to poll at a time + private static final int MAX_POLL_EVENTS = 512; + + // pollset ID + private final int pollset; + + // true if port is closed + private boolean closed; + + // socket pair used for wakeup + private final int sp[]; + + // socket pair used to indicate pending pollsetCtl calls + // Background info: pollsetCtl blocks when another thread is in a pollsetPoll call. + private final int ctlSp[]; + + // number of wakeups pending + private final AtomicInteger wakeupCount = new AtomicInteger(); + + // address of the poll array passed to pollset_poll + private final long address; + + // encapsulates an event for a channel + static class Event { + final PollableChannel channel; + final int events; + + Event(PollableChannel channel, int events) { + this.channel = channel; + this.events = events; + } + + PollableChannel channel() { return channel; } + int events() { return events; } + } + + // queue of events for cases that a polling thread dequeues more than one + // event + private final ArrayBlockingQueue queue; + private final Event NEED_TO_POLL = new Event(null, 0); + private final Event EXECUTE_TASK_OR_SHUTDOWN = new Event(null, 0); + private final Event CONTINUE_AFTER_CTL_EVENT = new Event(null, 0); + + // encapsulates a pollset control event for a file descriptor + static class ControlEvent { + final int fd; + final int events; + final boolean removeOnly; + int error = 0; + + ControlEvent(int fd, int events, boolean removeOnly) { + this.fd = fd; + this.events = events; + this.removeOnly = removeOnly; + } + + int fd() { return fd; } + int events() { return events; } + boolean removeOnly() { return removeOnly; } + int error() { return error; } + void setError(int error) { this.error = error; } + } + + // queue of control events that need to be processed + // (this object is also used for synchronization) + private final HashSet controlQueue = new HashSet(); + + // lock used to check whether a poll operation is ongoing + private final ReentrantLock controlLock = new ReentrantLock(); + + SerenityPollPort(AsynchronousChannelProvider provider, ThreadPool pool) + throws IOException + { + super(provider, pool); + + // open pollset + this.pollset = pollsetCreate(); + + // create socket pair for wakeup mechanism + int[] sv = new int[2]; + try { + socketpair(sv); + // register one end with pollset + pollsetCtl(pollset, PS_ADD, sv[0], Net.POLLIN); + } catch (IOException x) { + pollsetDestroy(pollset); + throw x; + } + this.sp = sv; + + // create socket pair for pollset control mechanism + sv = new int[2]; + try { + socketpair(sv); + // register one end with pollset + pollsetCtl(pollset, PS_ADD, sv[0], Net.POLLIN); + } catch (IOException x) { + pollsetDestroy(pollset); + throw x; + } + this.ctlSp = sv; + + // allocate the poll array + this.address = allocatePollArray(MAX_POLL_EVENTS); + + // create the queue and offer the special event to ensure that the first + // threads polls + this.queue = new ArrayBlockingQueue(MAX_POLL_EVENTS); + this.queue.offer(NEED_TO_POLL); + } + + SerenityPollPort start() { + startThreads(new EventHandlerTask()); + return this; + } + + /** + * Release all resources + */ + private void implClose() { + synchronized (this) { + if (closed) + return; + closed = true; + } + freePollArray(address); + close0(sp[0]); + close0(sp[1]); + close0(ctlSp[0]); + close0(ctlSp[1]); + pollsetDestroy(pollset); + } + + private void wakeup() { + if (wakeupCount.incrementAndGet() == 1) { + // write byte to socketpair to force wakeup + try { + interrupt(sp[1]); + } catch (IOException x) { + throw new AssertionError(x); + } + } + } + + @Override + void executeOnHandlerTask(Runnable task) { + synchronized (this) { + if (closed) + throw new RejectedExecutionException(); + offerTask(task); + wakeup(); + } + } + + @Override + void shutdownHandlerTasks() { + /* + * If no tasks are running then just release resources; otherwise + * write to the one end of the socketpair to wakeup any polling threads. + */ + int nThreads = threadCount(); + if (nThreads == 0) { + implClose(); + } else { + // send interrupt to each thread + while (nThreads-- > 0) { + wakeup(); + } + } + } + + // invoke by clients to register a file descriptor + @Override + void startPoll(int fd, int events) { + queueControlEvent(new ControlEvent(fd, events, false)); + } + + // Callback method for implementations that need special handling when fd is removed + @Override + protected void preUnregister(int fd) { + queueControlEvent(new ControlEvent(fd, 0, true)); + } + + // Add control event into queue and wait for completion. + // In case the control lock is free, this method also tries to apply the control change directly. + private void queueControlEvent(ControlEvent ev) { + // pollsetCtl blocks when a poll call is ongoing. This is very probable. + // Therefore we let the polling thread do the pollsetCtl call. + synchronized (controlQueue) { + controlQueue.add(ev); + // write byte to socketpair to force wakeup + try { + interrupt(ctlSp[1]); + } catch (IOException x) { + throw new AssertionError(x); + } + do { + // Directly empty queue if no poll call is ongoing. + if (controlLock.tryLock()) { + try { + processControlQueue(); + } finally { + controlLock.unlock(); + } + } else { + try { + // Do not starve in case the polling thread returned before + // we could write to ctlSp[1] but the polling thread did not + // release the control lock until we checked. Therefore, use + // a timed wait for the time being. + controlQueue.wait(100); + } catch (InterruptedException e) { + // ignore exception and try again + } + } + } while (controlQueue.contains(ev)); + } + if (ev.error() != 0) { + throw new AssertionError(); + } + } + + // Process all events currently stored in the control queue. + private void processControlQueue() { + synchronized (controlQueue) { + // TODO: this is ripped straight out of AIX implementation, who knows if it will work for Serenity + Iterator iter = controlQueue.iterator(); + while (iter.hasNext()) { + ControlEvent ev = iter.next(); + pollsetCtl(pollset, PS_DELETE, ev.fd(), 0); + if (!ev.removeOnly()) { + ev.setError(pollsetCtl(pollset, PS_MOD, ev.fd(), ev.events())); + } + iter.remove(); + } + controlQueue.notifyAll(); + } + } + + /* + * Task to process events from pollset and dispatch to the channel's + * onEvent handler. + * + * Events are retreived from pollset in batch and offered to a BlockingQueue + * where they are consumed by handler threads. A special "NEED_TO_POLL" + * event is used to signal one consumer to re-poll when all events have + * been consumed. + */ + private class EventHandlerTask implements Runnable { + private Event poll() throws IOException { + try { + for (;;) { + int n; + controlLock.lock(); + try { + n = pollsetPoll(pollset, address, MAX_POLL_EVENTS); + } finally { + controlLock.unlock(); + } + /* + * 'n' events have been read. Here we map them to their + * corresponding channel in batch and queue n-1 so that + * they can be handled by other handler threads. The last + * event is handled by this thread (and so is not queued). + */ + fdToChannelLock.readLock().lock(); + try { + while (n-- > 0) { + long eventAddress = getEvent(address, n); + int fd = getDescriptor(eventAddress); + + // To emulate one shot semantic we need to remove + // the file descriptor here. + if (fd != sp[0] && fd != ctlSp[0]) { + synchronized (controlQueue) { + pollsetCtl(pollset, PS_DELETE, fd, 0); + } + } + + // wakeup + if (fd == sp[0]) { + if (wakeupCount.decrementAndGet() == 0) { + // no more wakeups so drain pipe + drain1(sp[0]); + } + + // queue special event if there are more events + // to handle. + if (n > 0) { + queue.offer(EXECUTE_TASK_OR_SHUTDOWN); + continue; + } + return EXECUTE_TASK_OR_SHUTDOWN; + } + + // wakeup to process control event + if (fd == ctlSp[0]) { + synchronized (controlQueue) { + drain1(ctlSp[0]); + processControlQueue(); + } + if (n > 0) { + continue; + } + return CONTINUE_AFTER_CTL_EVENT; + } + + PollableChannel channel = fdToChannel.get(fd); + if (channel != null) { + int events = getRevents(eventAddress); + Event ev = new Event(channel, events); + + // n-1 events are queued; This thread handles + // the last one except for the wakeup + if (n > 0) { + queue.offer(ev); + } else { + return ev; + } + } + } + } finally { + fdToChannelLock.readLock().unlock(); + } + } + } finally { + // to ensure that some thread will poll when all events have + // been consumed + queue.offer(NEED_TO_POLL); + } + } + + public void run() { + Invoker.GroupAndInvokeCount myGroupAndInvokeCount = + Invoker.getGroupAndInvokeCount(); + final boolean isPooledThread = (myGroupAndInvokeCount != null); + boolean replaceMe = false; + Event ev; + try { + for (;;) { + // reset invoke count + if (isPooledThread) + myGroupAndInvokeCount.resetInvokeCount(); + + try { + replaceMe = false; + ev = queue.take(); + + // no events and this thread has been "selected" to + // poll for more. + if (ev == NEED_TO_POLL) { + try { + ev = poll(); + } catch (IOException x) { + x.printStackTrace(); + return; + } + } + } catch (InterruptedException x) { + continue; + } + + // contine after we processed a control event + if (ev == CONTINUE_AFTER_CTL_EVENT) { + continue; + } + + // handle wakeup to execute task or shutdown + if (ev == EXECUTE_TASK_OR_SHUTDOWN) { + Runnable task = pollTask(); + if (task == null) { + // shutdown request + return; + } + // run task (may throw error/exception) + replaceMe = true; + task.run(); + continue; + } + + // process event + try { + ev.channel().onEvent(ev.events(), isPooledThread); + } catch (Error x) { + replaceMe = true; throw x; + } catch (RuntimeException x) { + replaceMe = true; throw x; + } + } + } finally { + // last handler to exit when shutdown releases resources + int remaining = threadExit(this, replaceMe); + if (remaining == 0 && isShutdown()) { + implClose(); + } + } + } + } + + /** + * Allocates a poll array to handle up to {@code count} events. + */ + private static long allocatePollArray(int count) { + return unsafe.allocateMemory(count * SIZEOF_POLLFD); + } + + /** + * Free a poll array + */ + private static void freePollArray(long address) { + unsafe.freeMemory(address); + } + + /** + * Returns event[i]; + */ + private static long getEvent(long address, int i) { + return address + (SIZEOF_POLLFD*i); + } + + /** + * Returns event->fd + */ + private static int getDescriptor(long eventAddress) { + return unsafe.getInt(eventAddress + OFFSETOF_FD); + } + + /** + * Returns event->events + */ + private static int getEvents(long eventAddress) { + return unsafe.getChar(eventAddress + OFFSETOF_EVENTS); + } + + /** + * Returns event->revents + */ + private static int getRevents(long eventAddress) { + return unsafe.getChar(eventAddress + OFFSETOF_REVENTS); + } + + // -- Native methods -- + + private static native void init(); + + private static native int eventSize(); + + private static native int eventsOffset(); + + private static native int reventsOffset(); + + private static native int fdOffset(); + + private static native int pollsetCreate() throws IOException; + + private static native int pollsetCtl(int pollset, int opcode, int fd, int events); + + private static native int pollsetPoll(int pollset, long pollAddress, int numfds) + throws IOException; + + private static native void pollsetDestroy(int pollset); + + private static native void socketpair(int[] sv) throws IOException; + + private static native void interrupt(int fd) throws IOException; + + private static native void drain1(int fd) throws IOException; + + private static native void close0(int fd); +} diff --git a/src/java.base/serenity/classes/sun/nio/fs/DefaultFileSystemProvider.java b/src/java.base/serenity/classes/sun/nio/fs/DefaultFileSystemProvider.java new file mode 100644 index 000000000..b24f3de01 --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/fs/DefaultFileSystemProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, 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 + * 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.fs; + +import java.nio.file.FileSystem; + +/** + * Creates this platform's default FileSystemProvider. + */ + +public class DefaultFileSystemProvider { + private static final SerenityFileSystemProvider INSTANCE + = new SerenityFileSystemProvider(); + + private DefaultFileSystemProvider() { } + + /** + * Returns the platform's default file system provider. + */ + public static SerenityFileSystemProvider instance() { + return INSTANCE; + } + + /** + * Returns the platform's default file system. + */ + public static FileSystem theFileSystem() { + return INSTANCE.theFileSystem(); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/fs/SerenityFileStore.java b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileStore.java new file mode 100644 index 000000000..3f408ec9b --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileStore.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013 SAP SE. 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.fs; + +import java.nio.file.attribute.*; +import java.util.*; +import java.io.IOException; + +/** + * AIX implementation of FileStore + */ + +class SerenityFileStore + extends UnixFileStore +{ + + SerenityFileStore(UnixPath file) throws IOException { + super(file); + } + + SerenityFileStore(UnixFileSystem fs, UnixMountEntry entry) throws IOException { + super(fs, entry); + } + + /** + * Finds, and returns, the mount entry for the file system where the file + * resides. + */ + @Override + UnixMountEntry findMountEntry() throws IOException { + SerenityFileSystem fs = (SerenityFileSystem)file().getFileSystem(); + + // step 1: get realpath + UnixPath path = null; + try { + byte[] rp = UnixNativeDispatcher.realpath(file()); + path = new UnixPath(fs, rp); + } catch (UnixException x) { + x.rethrowAsIOException(file()); + } + + // step 2: find mount point + UnixPath parent = path.getParent(); + while (parent != null) { + UnixFileAttributes attrs = null; + try { + attrs = UnixFileAttributes.get(parent, true); + } catch (UnixException x) { + x.rethrowAsIOException(parent); + } + if (attrs.dev() != dev()) + break; + path = parent; + parent = parent.getParent(); + } + + // step 3: lookup mounted file systems + byte[] dir = path.asByteArray(); + for (UnixMountEntry entry: fs.getMountEntries()) { + if (Arrays.equals(dir, entry.dir())) + return entry; + } + + throw new IOException("Mount point not found"); + } + + @Override + protected boolean isExtendedAttributesEnabled(UnixPath path) { + return false; + } + + @Override + public boolean supportsFileAttributeView(Class type) { + return super.supportsFileAttributeView(type); + } + + @Override + public boolean supportsFileAttributeView(String name) { + return super.supportsFileAttributeView(name); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystem.java b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystem.java new file mode 100644 index 000000000..bee588a7e --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystem.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013 SAP SE. 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.fs; + +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.io.IOException; +import java.util.*; +import static sun.nio.fs.SerenityNativeDispatcher.*; + +/** + * AIX implementation of FileSystem + */ + +class SerenityFileSystem extends UnixFileSystem { + + SerenityFileSystem(UnixFileSystemProvider provider, String dir) { + super(provider, dir); + } + + @Override + public WatchService newWatchService() + throws IOException + { + return new PollingWatchService(); + } + + // lazy initialization of the list of supported attribute views + private static class SupportedFileFileAttributeViewsHolder { + static final Set supportedFileAttributeViews = + supportedFileAttributeViews(); + private static Set supportedFileAttributeViews() { + Set result = new HashSet(); + result.addAll(UnixFileSystem.standardFileAttributeViews()); + return Collections.unmodifiableSet(result); + } + } + + @Override + public Set supportedFileAttributeViews() { + return SupportedFileFileAttributeViewsHolder.supportedFileAttributeViews; + } + + @Override + void copyNonPosixAttributes(int ofd, int nfd) { + // TODO: Implement if needed. + } + + /** + * Returns object to iterate over the mount entries returned by mntctl + */ + @Override + Iterable getMountEntries() { + UnixMountEntry[] entries = null; + try { + entries = getmntctl(); + } catch (UnixException x) { + // nothing we can do + } + if (entries == null) { + return Collections.emptyList(); + } + return Arrays.asList(entries); + } + + @Override + FileStore getFileStore(UnixMountEntry entry) throws IOException { + return new SerenityFileStore(this, entry); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystemProvider.java b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystemProvider.java new file mode 100644 index 000000000..8582190af --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/fs/SerenityFileSystemProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013 SAP SE. 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.fs; + +import java.io.IOException; + +/** + * Serenity implementation of FileSystemProvider + */ + +class SerenityFileSystemProvider extends UnixFileSystemProvider { + public SerenityFileSystemProvider() { + super(); + } + + @Override + SerenityFileSystem newFileSystem(String dir) { + return new SerenityFileSystem(this, dir); + } + + /** + * @see sun.nio.fs.UnixFileSystemProvider#getFileStore(sun.nio.fs.UnixPath) + */ + @Override + SerenityFileStore getFileStore(UnixPath path) throws IOException { + return new SerenityFileStore(path); + } +} diff --git a/src/java.base/serenity/classes/sun/nio/fs/SerenityNativeDispatcher.java b/src/java.base/serenity/classes/sun/nio/fs/SerenityNativeDispatcher.java new file mode 100644 index 000000000..7c50b719c --- /dev/null +++ b/src/java.base/serenity/classes/sun/nio/fs/SerenityNativeDispatcher.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013 SAP SE. 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.fs; + +/** + * Serenity specific system calls. + */ + +class SerenityNativeDispatcher extends UnixNativeDispatcher { + private SerenityNativeDispatcher() { } + + /** + * Special implementation of 'getextmntent' (see SolarisNativeDispatcher) + * that returns all entries at once. + */ + static native UnixMountEntry[] getmntctl() throws UnixException; + + // initialize + private static native void init(); + + static { + jdk.internal.loader.BootLoader.loadLibrary("nio"); + init(); + } +} diff --git a/src/java.base/serenity/native/libnet/serenity_close.c b/src/java.base/serenity/native/libnet/serenity_close.c new file mode 100644 index 000000000..6a177bbb9 --- /dev/null +++ b/src/java.base/serenity/native/libnet/serenity_close.c @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2001, 2020, 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "jvm.h" +#include "net_util.h" + +/* + * Stack allocated by thread when doing blocking operation + */ +typedef struct threadEntry { + pthread_t thr; /* this thread */ + struct threadEntry *next; /* next thread */ + int intr; /* interrupted */ +} threadEntry_t; + +/* + * Heap allocated during initialized - one entry per fd + */ +typedef struct { + pthread_mutex_t lock; /* fd lock */ + threadEntry_t *threads; /* threads blocked on fd */ +} fdEntry_t; + +/* + * Signal to unblock thread + */ +static int sigWakeup = SIGIO; + +/* + * fdTable holds one entry per file descriptor, up to a certain + * maximum. + * Theoretically, the number of possible file descriptors can get + * large, though usually it does not. Entries for small value file + * descriptors are kept in a simple table, which covers most scenarios. + * Entries for large value file descriptors are kept in an overflow + * table, which is organized as a sparse two dimensional array whose + * slabs are allocated on demand. This covers all corner cases while + * keeping memory consumption reasonable. + */ + +/* Base table for low value file descriptors */ +static fdEntry_t* fdTable = NULL; +/* Maximum size of base table (in number of entries). */ +static const int fdTableMaxSize = 0x1000; /* 4K */ +/* Actual size of base table (in number of entries) */ +static int fdTableLen = 0; +/* Max. theoretical number of file descriptors on system. */ +static int fdLimit = 0; + +/* Overflow table, should base table not be large enough. Organized as + * an array of n slabs, each holding 64k entries. + */ +static fdEntry_t** fdOverflowTable = NULL; +/* Number of slabs in the overflow table */ +static int fdOverflowTableLen = 0; +/* Number of entries in one slab */ +static const int fdOverflowTableSlabSize = 0x10000; /* 64k */ +pthread_mutex_t fdOverflowTableLock = PTHREAD_MUTEX_INITIALIZER; + +/* + * Null signal handler + */ +static void sig_wakeup(int sig) { +} + +/* + * Initialization routine (executed when library is loaded) + * Allocate fd tables and sets up signal handler. + */ +static void __attribute((constructor)) init() { + struct rlimit nbr_files; + sigset_t sigset; + struct sigaction sa; + int i = 0; + + /* Determine the maximum number of possible file descriptors. */ + if (-1 == getrlimit(RLIMIT_NOFILE, &nbr_files)) { + fprintf(stderr, "library initialization failed - " + "unable to get max # of allocated fds\n"); + abort(); + } + if (nbr_files.rlim_max != RLIM_INFINITY) { + fdLimit = nbr_files.rlim_max; + } else { + /* We just do not know. */ + fdLimit = INT_MAX; + } + + /* Allocate table for low value file descriptors. */ + fdTableLen = fdLimit < fdTableMaxSize ? fdLimit : fdTableMaxSize; + fdTable = (fdEntry_t*) calloc(fdTableLen, sizeof(fdEntry_t)); + if (fdTable == NULL) { + fprintf(stderr, "library initialization failed - " + "unable to allocate file descriptor table - out of memory"); + abort(); + } else { + for (i = 0; i < fdTableLen; i ++) { + pthread_mutex_init(&fdTable[i].lock, NULL); + } + } + + /* Allocate overflow table, if needed */ + if (fdLimit > fdTableMaxSize) { + fdOverflowTableLen = ((fdLimit - fdTableMaxSize) / fdOverflowTableSlabSize) + 1; + fdOverflowTable = (fdEntry_t**) calloc(fdOverflowTableLen, sizeof(fdEntry_t*)); + if (fdOverflowTable == NULL) { + fprintf(stderr, "library initialization failed - " + "unable to allocate file descriptor overflow table - out of memory"); + abort(); + } + } + + /* + * Setup the signal handler + */ + sa.sa_handler = sig_wakeup; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(sigWakeup, &sa, NULL); + + sigemptyset(&sigset); + sigaddset(&sigset, sigWakeup); + sigprocmask(SIG_UNBLOCK, &sigset, NULL); +} + +/* + * Return the fd table for this fd. + */ +static inline fdEntry_t *getFdEntry(int fd) +{ + fdEntry_t* result = NULL; + + if (fd < 0) { + return NULL; + } + + /* This should not happen. If it does, our assumption about + * max. fd value was wrong. */ + assert(fd < fdLimit); + + if (fd < fdTableMaxSize) { + /* fd is in base table. */ + assert(fd < fdTableLen); + result = &fdTable[fd]; + } else { + /* fd is in overflow table. */ + const int indexInOverflowTable = fd - fdTableMaxSize; + const int rootindex = indexInOverflowTable / fdOverflowTableSlabSize; + const int slabindex = indexInOverflowTable % fdOverflowTableSlabSize; + fdEntry_t* slab = NULL; + assert(rootindex < fdOverflowTableLen); + assert(slabindex < fdOverflowTableSlabSize); + pthread_mutex_lock(&fdOverflowTableLock); + /* Allocate new slab in overflow table if needed */ + if (fdOverflowTable[rootindex] == NULL) { + fdEntry_t* const newSlab = + (fdEntry_t*)calloc(fdOverflowTableSlabSize, sizeof(fdEntry_t)); + if (newSlab == NULL) { + fprintf(stderr, "Unable to allocate file descriptor overflow" + " table slab - out of memory"); + pthread_mutex_unlock(&fdOverflowTableLock); + abort(); + } else { + int i; + for (i = 0; i < fdOverflowTableSlabSize; i ++) { + pthread_mutex_init(&newSlab[i].lock, NULL); + } + fdOverflowTable[rootindex] = newSlab; + } + } + pthread_mutex_unlock(&fdOverflowTableLock); + slab = fdOverflowTable[rootindex]; + result = &slab[slabindex]; + } + + return result; + +} + + +/* + * Start a blocking operation :- + * Insert thread onto thread list for the fd. + */ +static inline void startOp(fdEntry_t *fdEntry, threadEntry_t *self) +{ + self->thr = pthread_self(); + self->intr = 0; + + pthread_mutex_lock(&(fdEntry->lock)); + { + self->next = fdEntry->threads; + fdEntry->threads = self; + } + pthread_mutex_unlock(&(fdEntry->lock)); +} + +/* + * End a blocking operation :- + * Remove thread from thread list for the fd + * If fd has been interrupted then set errno to EBADF + */ +static inline void endOp + (fdEntry_t *fdEntry, threadEntry_t *self) +{ + int orig_errno = errno; + pthread_mutex_lock(&(fdEntry->lock)); + { + threadEntry_t *curr, *prev=NULL; + curr = fdEntry->threads; + while (curr != NULL) { + if (curr == self) { + if (curr->intr) { + orig_errno = EBADF; + } + if (prev == NULL) { + fdEntry->threads = curr->next; + } else { + prev->next = curr->next; + } + break; + } + prev = curr; + curr = curr->next; + } + } + pthread_mutex_unlock(&(fdEntry->lock)); + errno = orig_errno; +} + +/* + * Close or dup2 a file descriptor ensuring that all threads blocked on + * the file descriptor are notified via a wakeup signal. + * + * fd1 < 0 => close(fd2) + * fd1 >= 0 => dup2(fd1, fd2) + * + * Returns -1 with errno set if operation fails. + */ +static int closefd(int fd1, int fd2) { + int rv, orig_errno; + fdEntry_t *fdEntry = getFdEntry(fd2); + if (fdEntry == NULL) { + errno = EBADF; + return -1; + } + + /* + * Lock the fd to hold-off additional I/O on this fd. + */ + pthread_mutex_lock(&(fdEntry->lock)); + + { + /* + * Send a wakeup signal to all threads blocked on this + * file descriptor. + */ + threadEntry_t *curr = fdEntry->threads; + while (curr != NULL) { + curr->intr = 1; + pthread_kill( curr->thr, sigWakeup ); + curr = curr->next; + } + + /* + * And close/dup the file descriptor + * (restart if interrupted by signal) + */ + do { + if (fd1 < 0) { + rv = close(fd2); + } else { + rv = dup2(fd1, fd2); + } + } while (rv == -1 && errno == EINTR); + + } + + /* + * Unlock without destroying errno + */ + orig_errno = errno; + pthread_mutex_unlock(&(fdEntry->lock)); + errno = orig_errno; + + return rv; +} + +/* + * Wrapper for dup2 - same semantics as dup2 system call except + * that any threads blocked in an I/O system call on fd2 will be + * preempted and return -1/EBADF; + */ +int NET_Dup2(int fd, int fd2) { + if (fd < 0) { + errno = EBADF; + return -1; + } + return closefd(fd, fd2); +} + +/* + * Wrapper for close - same semantics as close system call + * except that any threads blocked in an I/O on fd will be + * preempted and the I/O system call will return -1/EBADF. + */ +int NET_SocketClose(int fd) { + return closefd(-1, fd); +} + +/************** Basic I/O operations here ***************/ + +/* + * Macro to perform a blocking IO operation. Restarts + * automatically if interrupted by signal (other than + * our wakeup signal) + */ +#define BLOCKING_IO_RETURN_INT(FD, FUNC) { \ + int ret; \ + threadEntry_t self; \ + fdEntry_t *fdEntry = getFdEntry(FD); \ + if (fdEntry == NULL) { \ + errno = EBADF; \ + return -1; \ + } \ + do { \ + startOp(fdEntry, &self); \ + ret = FUNC; \ + endOp(fdEntry, &self); \ + } while (ret == -1 && errno == EINTR); \ + return ret; \ +} + +int NET_Read(int s, void* buf, size_t len) { + BLOCKING_IO_RETURN_INT( s, recv(s, buf, len, 0) ); +} + +int NET_NonBlockingRead(int s, void* buf, size_t len) { + BLOCKING_IO_RETURN_INT( s, recv(s, buf, len, MSG_DONTWAIT)); +} + +int NET_RecvFrom(int s, void *buf, int len, unsigned int flags, + struct sockaddr *from, socklen_t *fromlen) { + BLOCKING_IO_RETURN_INT( s, recvfrom(s, buf, len, flags, from, fromlen) ); +} + +int NET_Send(int s, void *msg, int len, unsigned int flags) { + BLOCKING_IO_RETURN_INT( s, send(s, msg, len, flags) ); +} + +int NET_SendTo(int s, const void *msg, int len, unsigned int + flags, const struct sockaddr *to, int tolen) { + BLOCKING_IO_RETURN_INT( s, sendto(s, msg, len, flags, to, tolen) ); +} + +int NET_Accept(int s, struct sockaddr *addr, socklen_t *addrlen) { + BLOCKING_IO_RETURN_INT( s, accept(s, addr, addrlen) ); +} + +int NET_Connect(int s, struct sockaddr *addr, int addrlen) { + BLOCKING_IO_RETURN_INT( s, connect(s, addr, addrlen) ); +} + +int NET_Poll(struct pollfd *ufds, unsigned int nfds, int timeout) { + BLOCKING_IO_RETURN_INT( ufds[0].fd, poll(ufds, nfds, timeout) ); +} + +/* + * Wrapper for poll(s, timeout). + * Auto restarts with adjusted timeout if interrupted by + * signal other than our wakeup signal. + */ +int NET_Timeout(JNIEnv *env, int s, long timeout, jlong nanoTimeStamp) { + jlong prevNanoTime = nanoTimeStamp; + jlong nanoTimeout = (jlong)timeout * NET_NSEC_PER_MSEC; + fdEntry_t *fdEntry = getFdEntry(s); + + /* + * Check that fd hasn't been closed. + */ + if (fdEntry == NULL) { + errno = EBADF; + return -1; + } + + for(;;) { + struct pollfd pfd; + int rv; + threadEntry_t self; + + /* + * Poll the fd. If interrupted by our wakeup signal + * errno will be set to EBADF. + */ + pfd.fd = s; + pfd.events = POLLIN | POLLERR; + + startOp(fdEntry, &self); + rv = poll(&pfd, 1, nanoTimeout / NET_NSEC_PER_MSEC); + endOp(fdEntry, &self); + /* + * If interrupted then adjust timeout. If timeout + * has expired return 0 (indicating timeout expired). + */ + if (rv < 0 && errno == EINTR) { + if (timeout > 0) { + jlong newNanoTime = JVM_NanoTime(env, 0); + nanoTimeout -= newNanoTime - prevNanoTime; + if (nanoTimeout < NET_NSEC_PER_MSEC) { + return 0; + } + prevNanoTime = newNanoTime; + } else { + continue; // timeout is -1, so loop again. + } + } else { + return rv; + } + } +} diff --git a/src/jdk.attach/serenity/classes/sun/tools/attach/AttachProviderImpl.java b/src/jdk.attach/serenity/classes/sun/tools/attach/AttachProviderImpl.java new file mode 100644 index 000000000..2f6fc4d4d --- /dev/null +++ b/src/jdk.attach/serenity/classes/sun/tools/attach/AttachProviderImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013 SAP SE. 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.tools.attach; + +import com.sun.tools.attach.VirtualMachine; +import com.sun.tools.attach.VirtualMachineDescriptor; +import com.sun.tools.attach.AttachNotSupportedException; +import java.io.IOException; + +// Based on linux/classes/sun/tools/attach/AttachProviderImpl.java. + +/* + * An AttachProvider implementation for Aix that uses a UNIX domain + * socket. + */ +public class AttachProviderImpl extends HotSpotAttachProvider { + + public AttachProviderImpl() { + } + + public String name() { + return "sun"; + } + + public String type() { + return "socket"; + } + + public VirtualMachine attachVirtualMachine(String vmid) + throws AttachNotSupportedException, IOException + { + checkAttachPermission(); + + // AttachNotSupportedException will be thrown if the target VM can be determined + // to be not attachable. + testAttachable(vmid); + + return new VirtualMachineImpl(this, vmid); + } + + public VirtualMachine attachVirtualMachine(VirtualMachineDescriptor vmd) + throws AttachNotSupportedException, IOException + { + if (vmd.provider() != this) { + throw new AttachNotSupportedException("provider mismatch"); + } + // To avoid re-checking if the VM if attachable, we check if the descriptor + // is for a hotspot VM - these descriptors are created by the listVirtualMachines + // implementation which only returns a list of attachable VMs. + if (vmd instanceof HotSpotVirtualMachineDescriptor) { + assert ((HotSpotVirtualMachineDescriptor)vmd).isAttachable(); + checkAttachPermission(); + return new VirtualMachineImpl(this, vmd.id()); + } else { + return attachVirtualMachine(vmd.id()); + } + } + +} diff --git a/src/jdk.attach/serenity/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/serenity/classes/sun/tools/attach/VirtualMachineImpl.java new file mode 100644 index 000000000..0c432edee --- /dev/null +++ b/src/jdk.attach/serenity/classes/sun/tools/attach/VirtualMachineImpl.java @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2019 SAP SE. 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.tools.attach; + +import com.sun.tools.attach.AttachOperationFailedException; +import com.sun.tools.attach.AgentLoadException; +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.spi.AttachProvider; + +import java.io.InputStream; +import java.io.IOException; +import java.io.File; + +/* + * Aix implementation of HotSpotVirtualMachine + */ +public class VirtualMachineImpl extends HotSpotVirtualMachine { + // "/tmp" is used as a global well-known location for the files + // .java_pid. and .attach_pid. It is important that this + // location is the same for all processes, otherwise the tools + // will not be able to find all Hotspot processes. + // Any changes to this needs to be synchronized with HotSpot. + private static final String tmpdir = "/tmp"; + String socket_path; + + /** + * Attaches to the target VM + */ + VirtualMachineImpl(AttachProvider provider, String vmid) + throws AttachNotSupportedException, IOException + { + super(provider, vmid); + + // This provider only understands pids + int pid; + try { + pid = Integer.parseInt(vmid); + if (pid < 1) { + throw new NumberFormatException(); + } + } catch (NumberFormatException x) { + throw new AttachNotSupportedException("Invalid process identifier: " + vmid); + } + + // Find the socket file. If not found then we attempt to start the + // attach mechanism in the target VM by sending it a QUIT signal. + // Then we attempt to find the socket file again. + File socket_file = new File(tmpdir, ".java_pid" + pid); + socket_path = socket_file.getPath(); + if (!socket_file.exists()) { + // Keep canonical version of File, to delete, in case target process ends and /proc link has gone: + File f = createAttachFile(pid).getCanonicalFile(); + try { + sendQuitTo(pid); + + // give the target VM time to start the attach mechanism + final int delay_step = 100; + final long timeout = attachTimeout(); + long time_spend = 0; + long delay = 0; + do { + // Increase timeout on each attempt to reduce polling + delay += delay_step; + try { + Thread.sleep(delay); + } catch (InterruptedException x) { } + + time_spend += delay; + if (time_spend > timeout/2 && !socket_file.exists()) { + // Send QUIT again to give target VM the last chance to react + sendQuitTo(pid); + } + } while (time_spend <= timeout && !socket_file.exists()); + if (!socket_file.exists()) { + throw new AttachNotSupportedException( + String.format("Unable to open socket file %s: " + + "target process %d doesn't respond within %dms " + + "or HotSpot VM not loaded", socket_path, pid, + time_spend)); + } + } finally { + f.delete(); + } + } + + // Check that the file owner/permission to avoid attaching to + // bogus process + checkPermissions(socket_path); + + // Check that we can connect to the process + // - this ensures we throw the permission denied error now rather than + // later when we attempt to enqueue a command. + int s = socket(); + try { + connect(s, socket_path); + } finally { + close(s); + } + } + + /** + * Detach from the target VM + */ + public void detach() throws IOException { + synchronized (this) { + if (socket_path != null) { + socket_path = null; + } + } + } + + // protocol version + private final static String PROTOCOL_VERSION = "1"; + + // known errors + private final static int ATTACH_ERROR_BADVERSION = 101; + + /** + * Execute the given command in the target VM. + */ + InputStream execute(String cmd, Object ... args) throws AgentLoadException, IOException { + assert args.length <= 3; // includes null + + // did we detach? + synchronized (this) { + if (socket_path == null) { + throw new IOException("Detached from target VM"); + } + } + + // create UNIX socket + int s = socket(); + + // connect to target VM + try { + connect(s, socket_path); + } catch (IOException x) { + close(s); + throw x; + } + + IOException ioe = null; + + // connected - write request + // + try { + writeString(s, PROTOCOL_VERSION); + writeString(s, cmd); + + for (int i = 0; i < 3; i++) { + if (i < args.length && args[i] != null) { + writeString(s, (String)args[i]); + } else { + writeString(s, ""); + } + } + } catch (IOException x) { + ioe = x; + } + + + // Create an input stream to read reply + SocketInputStream sis = new SocketInputStream(s); + + // Read the command completion status + int completionStatus; + try { + completionStatus = readInt(sis); + } catch (IOException x) { + sis.close(); + if (ioe != null) { + throw ioe; + } else { + throw x; + } + } + + if (completionStatus != 0) { + // read from the stream and use that as the error message + String message = readErrorMessage(sis); + sis.close(); + + // In the event of a protocol mismatch then the target VM + // returns a known error so that we can throw a reasonable + // error. + if (completionStatus == ATTACH_ERROR_BADVERSION) { + throw new IOException("Protocol mismatch with target VM"); + } + + // Special-case the "load" command so that the right exception is + // thrown. + if (cmd.equals("load")) { + String msg = "Failed to load agent library"; + if (!message.isEmpty()) + msg += ": " + message; + throw new AgentLoadException(msg); + } else { + if (message.isEmpty()) + message = "Command failed in target VM"; + throw new AttachOperationFailedException(message); + } + } + + // Return the input stream so that the command output can be read + return sis; + } + + /* + * InputStream for the socket connection to get target VM + */ + private class SocketInputStream extends InputStream { + int s; + + public SocketInputStream(int s) { + this.s = s; + } + + public synchronized int read() throws IOException { + byte b[] = new byte[1]; + int n = this.read(b, 0, 1); + if (n == 1) { + return b[0] & 0xff; + } else { + return -1; + } + } + + public synchronized int read(byte[] bs, int off, int len) throws IOException { + if ((off < 0) || (off > bs.length) || (len < 0) || + ((off + len) > bs.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) + return 0; + + return VirtualMachineImpl.read(s, bs, off, len); + } + + public synchronized void close() throws IOException { + if (s != -1) { + int toClose = s; + s = -1; + VirtualMachineImpl.close(toClose); + } + } + } + + // On Aix a simple handshake is used to start the attach mechanism + // if not already started. The client creates a .attach_pid file in the + // target VM's working directory (or temp directory), and the SIGQUIT handler + // checks for the file. + private File createAttachFile(int pid) throws IOException { + String fn = ".attach_pid" + pid; + String path = "/proc/" + pid + "/cwd/" + fn; + File f = new File(path); + try { + f.createNewFile(); + } catch (IOException x) { + f = new File(tmpdir, fn); + f.createNewFile(); + } + return f; + } + + /* + * Write/sends the given to the target VM. String is transmitted in + * UTF-8 encoding. + */ + private void writeString(int fd, String s) throws IOException { + if (s.length() > 0) { + byte b[]; + try { + b = s.getBytes("UTF-8"); + } catch (java.io.UnsupportedEncodingException x) { + throw new InternalError(x); + } + VirtualMachineImpl.write(fd, b, 0, b.length); + } + byte b[] = new byte[1]; + b[0] = 0; + write(fd, b, 0, 1); + } + + + //-- native methods + + static native void sendQuitTo(int pid) throws IOException; + + static native void checkPermissions(String path) throws IOException; + + static native int socket() throws IOException; + + static native void connect(int fd, String path) throws IOException; + + static native void close(int fd) throws IOException; + + static native int read(int fd, byte buf[], int off, int bufLen) throws IOException; + + static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; + + static { + System.loadLibrary("attach"); + } +} diff --git a/src/jdk.attach/serenity/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/serenity/native/libattach/VirtualMachineImpl.c new file mode 100644 index 000000000..d20a6f012 --- /dev/null +++ b/src/jdk.attach/serenity/native/libattach/VirtualMachineImpl.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2005, 2021, 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. + */ + +#include "jni_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun_tools_attach_VirtualMachineImpl.h" + +#define RESTARTABLE(_cmd, _result) do { \ + do { \ + _result = _cmd; \ + } while((_result == -1) && (errno == EINTR)); \ +} while(0) + +#define ROOT_UID 0 + +/* + * Declare library specific JNI_Onload entry if static build + */ +DEF_STATIC_JNI_OnLoad + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: socket + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_socket + (JNIEnv *env, jclass cls) +{ + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + JNU_ThrowIOExceptionWithLastError(env, "socket"); + } + return (jint)fd; +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: connect + * Signature: (ILjava/lang/String;)I + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect + (JNIEnv *env, jclass cls, jint fd, jstring path) +{ + jboolean isCopy; + const char* p = GetStringPlatformChars(env, path, &isCopy); + if (p != NULL) { + struct sockaddr_un addr; + int err = 0; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + /* strncpy is safe because addr.sun_path was zero-initialized before. */ + strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1); + + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + err = errno; + } + + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, p); + } + + /* + * If the connect failed then we throw the appropriate exception + * here (can't throw it before releasing the string as can't call + * JNI with pending exception) + */ + if (err != 0) { + if (err == ENOENT) { + JNU_ThrowByName(env, "java/io/FileNotFoundException", NULL); + } else { + char* msg = strdup(strerror(err)); + JNU_ThrowIOException(env, msg); + if (msg != NULL) { + free(msg); + } + } + } + } +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: sendQuitTo + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_sendQuitTo + (JNIEnv *env, jclass cls, jint pid) +{ + if (kill((pid_t)pid, SIGQUIT)) { + JNU_ThrowIOExceptionWithLastError(env, "kill"); + } +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: checkPermissions + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions + (JNIEnv *env, jclass cls, jstring path) +{ + jboolean isCopy; + const char* p = GetStringPlatformChars(env, path, &isCopy); + if (p != NULL) { + struct stat sb; + uid_t uid, gid; + int res; + + memset(&sb, 0, sizeof(struct stat)); + + /* + * Check that the path is owned by the effective uid/gid of this + * process. Also check that group/other access is not allowed. + */ + uid = geteuid(); + gid = getegid(); + + res = stat(p, &sb); + if (res != 0) { + /* save errno */ + res = errno; + } + + if (res == 0) { + char msg[100]; + jboolean isError = JNI_FALSE; + if (sb.st_uid != uid && uid != ROOT_UID) { + snprintf(msg, sizeof(msg), + "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid); + isError = JNI_TRUE; + } else if (sb.st_gid != gid && uid != ROOT_UID) { + snprintf(msg, sizeof(msg), + "file's group should be the current group (which is %d) but the group is %d", gid, sb.st_gid); + isError = JNI_TRUE; + } else if ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) { + snprintf(msg, sizeof(msg), + "file should only be readable and writable by the owner but has 0%03o access", sb.st_mode & 0777); + isError = JNI_TRUE; + } + if (isError) { + char buf[256]; + snprintf(buf, sizeof(buf), "well-known file %s is not secure: %s", p, msg); + JNU_ThrowIOException(env, buf); + } + } else { + char* msg = strdup(strerror(res)); + JNU_ThrowIOException(env, msg); + if (msg != NULL) { + free(msg); + } + } + + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, p); + } + } +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: close + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_close + (JNIEnv *env, jclass cls, jint fd) +{ + int res; + shutdown(fd, SHUT_RDWR); + RESTARTABLE(close(fd), res); +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: read + * Signature: (I[BI)I + */ +JNIEXPORT jint JNICALL Java_sun_tools_attach_VirtualMachineImpl_read + (JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint baLen) +{ + unsigned char buf[128]; + size_t len = sizeof(buf); + ssize_t n; + + size_t remaining = (size_t)(baLen - off); + if (len > remaining) { + len = remaining; + } + + RESTARTABLE(read(fd, buf, len), n); + if (n == -1) { + JNU_ThrowIOExceptionWithLastError(env, "read"); + } else { + if (n == 0) { + n = -1; // EOF + } else { + (*env)->SetByteArrayRegion(env, ba, off, (jint)n, (jbyte *)(buf)); + } + } + return n; +} + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: write + * Signature: (I[B)V + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write + (JNIEnv *env, jclass cls, jint fd, jbyteArray ba, jint off, jint bufLen) +{ + size_t remaining = bufLen; + do { + unsigned char buf[128]; + size_t len = sizeof(buf); + int n; + + if (len > remaining) { + len = remaining; + } + (*env)->GetByteArrayRegion(env, ba, off, len, (jbyte *)buf); + + RESTARTABLE(write(fd, buf, len), n); + if (n > 0) { + off += n; + remaining -= n; + } else { + JNU_ThrowIOExceptionWithLastError(env, "write"); + return; + } + + } while (remaining > 0); +} + +/* + * Class: sun_tools_attach_BSDVirtualMachine + * Method: createAttachFile + * Signature: (Ljava.lang.String;)V + */ +JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_createAttachFile0(JNIEnv *env, jclass cls, jstring path) +{ + const char* _path; + jboolean isCopy; + int fd, rc; + + _path = GetStringPlatformChars(env, path, &isCopy); + if (_path == NULL) { + JNU_ThrowIOException(env, "Must specify a path"); + return; + } + + RESTARTABLE(open(_path, O_CREAT | O_EXCL, S_IWUSR | S_IRUSR), fd); + if (fd == -1) { + /* release p here before we throw an I/O exception */ + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, _path); + } + JNU_ThrowIOExceptionWithLastError(env, "open"); + return; + } + + RESTARTABLE(chown(_path, geteuid(), getegid()), rc); + + RESTARTABLE(close(fd), rc); + + /* release p here */ + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, _path); + } +} + +/* + * Class: sun_tools_attach_BSDVirtualMachine + * Method: getTempDir + * Signature: (V)Ljava.lang.String; + */ +JNIEXPORT jstring JNICALL Java_sun_tools_attach_VirtualMachineImpl_getTempDir(JNIEnv *env, jclass cls) +{ + // This must be hard coded because it's the system's temporary + // directory not the java application's temp directory, ala java.io.tmpdir. + +#ifdef __APPLE__ + // macosx has a secure per-user temporary directory. + // Don't cache the result as this is only called once. + char path[PATH_MAX]; + int pathSize = confstr(_CS_DARWIN_USER_TEMP_DIR, path, PATH_MAX); + if (pathSize == 0 || pathSize > PATH_MAX) { + strlcpy(path, "/tmp", sizeof(path)); + } + return JNU_NewStringPlatform(env, path); +#else /* __APPLE__ */ + return (*env)->NewStringUTF(env, "/tmp"); +#endif /* __APPLE__ */ +}