1 /*
  2  * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package sun.nio.ch;
 26 
 27 import java.io.FileDescriptor;
 28 import java.io.IOException;
 29 import java.io.InputStream;
 30 import java.io.OutputStream;
 31 import java.nio.ByteBuffer;
 32 import java.util.Objects;
 33 import java.util.concurrent.locks.ReentrantLock;
 34 import jdk.internal.access.SharedSecrets;
 35 import jdk.internal.misc.VirtualThreads;
 36 
 37 public class ConsoleStreams {
 38     private ConsoleStreams() { }
 39 
 40     public static final ConsoleInputStream in = new ConsoleInputStream(FileDescriptor.in);
 41     public static final ConsoleOutputStream out = new ConsoleOutputStream(FileDescriptor.out);
 42     public static final ConsoleOutputStream err = new ConsoleOutputStream(FileDescriptor.err);
 43 
 44     // Holder class to avoid loading of NativeDispatcher during initPhase1
 45     private static class Holder {
 46         static final NativeDispatcher nd = new FileDispatcherImpl();
 47     }
 48 
 49     private static NativeDispatcher nd() {
 50         return Holder.nd;
 51     }
 52 
 53     private static void park(FileDescriptor fd, int event) throws IOException {
 54         assert Thread.currentThread().isVirtual();
 55         int fdVal = SharedSecrets.getJavaIOFileDescriptorAccess().get(fd);
 56         Poller.register(fdVal, event);
 57         try {
 58             VirtualThreads.park();
 59         } finally {
 60             Poller.deregister(fdVal, event);
 61         }
 62     }
 63 
 64     /**
 65      * Returns true if the out or err streams are locked by the given thread.
 66      */
 67     public static boolean isOutOrErrLocked(Thread thread) {
 68         return (thread == out.currentWriter() || thread == err.currentWriter());
 69     }
 70 
 71     private static class ConsoleInputStream extends InputStream {
 72         private final FileDescriptor fd;
 73         private final ReentrantLock readLock = new ReentrantLock();
 74 
 75         public ConsoleInputStream(FileDescriptor fd) {
 76             this.fd = fd;
 77         }
 78 
 79         private int tryRead(byte[] b, int off, int len) throws IOException {
 80             ByteBuffer dst = Util.getTemporaryDirectBuffer(len);
 81             assert dst.position() == 0;
 82             try {
 83                 int n = nd().read(fd, ((DirectBuffer)dst).address(), len);
 84                 if (n > 0) {
 85                     dst.get(b, off, n);
 86                 }
 87                 return n;
 88             } finally {
 89                 Util.offerFirstTemporaryDirectBuffer(dst);
 90             }
 91         }
 92 
 93         @Override
 94         public int read() throws IOException {
 95             byte[] a = new byte[1];
 96             int n = read(a, 0, 1);
 97             return (n > 0) ? (a[0] & 0xff) : -1;
 98         }
 99 
100         @Override
101         public int read(byte[] b, int off, int len) throws IOException {
102             Objects.checkFromIndexSize(off, len, b.length);
103             if (len == 0) {
104                 return 0;
105             } else {
106                 readLock.lock();
107                 try {
108                     if (Thread.currentThread().isVirtual()) {
109                         park(fd, Net.POLLIN);
110                     }
111                     int n;
112                     do {
113                         n = tryRead(b, off, len);
114                     } while (n == IOStatus.INTERRUPTED);
115                     return n;
116                 } finally {
117                     readLock.unlock();
118                 }
119             }
120         }
121 
122         @Override
123         public void close() throws IOException {
124             // not fully implemented yet, should dup to /dev/null
125             nd().close(fd);
126         }
127     }
128 
129     private static class ConsoleOutputStream extends OutputStream {
130         private final FileDescriptor fd;
131         private final ReentrantLock writeLock = new ReentrantLock();
132         private volatile Thread writer;
133 
134         ConsoleOutputStream(FileDescriptor fd) {
135             this.fd = fd;
136         }
137 
138         Thread currentWriter() {
139             return writer;
140         }
141 
142         private int tryWrite(byte[] b, int off, int len) throws IOException {
143             ByteBuffer src = Util.getTemporaryDirectBuffer(len);
144             assert src.position() == 0;
145             try {
146                 src.put(b, off, len);
147                 return nd().write(fd, ((DirectBuffer)src).address(), len);
148             } finally {
149                 Util.offerFirstTemporaryDirectBuffer(src);
150             }
151         }
152 
153         private int implWrite(byte[] b, int off, int len) throws IOException {
154             int n;
155             Thread thread = Thread.currentThread();
156             if (thread.isVirtual()) {
157                 park(fd, Net.POLLOUT);
158             }
159             do {
160                 n = tryWrite(b, off, len);
161             } while (n == IOStatus.INTERRUPTED);
162             return n;
163         }
164 
165         @Override
166         public void write(int b) throws IOException {
167             byte[] a = new byte[]{(byte) b};
168             write(a, 0, 1);
169         }
170 
171         @Override
172         public void write(byte[] b, int off, int len) throws IOException {
173             Objects.checkFromIndexSize(off, len, b.length);
174             Thread thread = Thread.currentThread();
175             if (len > 0) {
176                 writeLock.lock();
177                 try {
178                     writer = thread;
179                     int pos = off;
180                     int end = off + len;
181                     while (pos < end) {
182                         int size = Math.min((end - pos), 16*1024);
183                         int n = implWrite(b, pos, size);
184                         pos += n;
185                     }
186                 } finally {
187                     writer = null;
188                     writeLock.unlock();
189                 }
190             }
191         }
192 
193         @Override
194         public void close() throws IOException {
195             // not fully implemented yet, should dup to /dev/null
196             nd().close(fd);
197         }
198     }
199 }