diff --git a/src/java.base/share/classes/java/io/FileInputStream.java b/src/java.base/share/classes/java/io/FileInputStream.java index 180b2e416a9..4210a0f56b6 100644 --- a/src/java.base/share/classes/java/io/FileInputStream.java +++ b/src/java.base/share/classes/java/io/FileInputStream.java @@ -29,6 +29,7 @@ import java.nio.channels.FileChannel; import java.util.Arrays; import jdk.internal.util.ArraysSupport; import jdk.internal.event.FileReadEvent; +import jdk.internal.vm.annotation.Stable; import sun.nio.ch.FileChannelImpl; /** @@ -81,6 +82,10 @@ public class FileInputStream extends InputStream private volatile boolean closed; + // This field indicates whether the file is a regular file as some + // operations need the current position which requires seeking + private @Stable Boolean isRegularFile; + /** * Creates a {@code FileInputStream} to read from an existing file * named by the path name {@code name}. @@ -331,6 +336,9 @@ public class FileInputStream extends InputStream @Override public byte[] readAllBytes() throws IOException { + if (!isRegularFile()) + return super.readAllBytes(); + long length = length(); long position = position(); long size = length - position; @@ -382,6 +390,9 @@ public class FileInputStream extends InputStream if (len == 0) return new byte[0]; + if (!isRegularFile()) + return super.readNBytes(len); + long length = length(); long position = position(); long size = length - position; @@ -418,7 +429,7 @@ public class FileInputStream extends InputStream @Override public long transferTo(OutputStream out) throws IOException { long transferred = 0L; - if (out instanceof FileOutputStream fos) { + if (out instanceof FileOutputStream fos && isRegularFile()) { FileChannel fc = getChannel(); long pos = fc.position(); transferred = fc.transferTo(pos, Long.MAX_VALUE, fos.getChannel()); @@ -471,7 +482,10 @@ public class FileInputStream extends InputStream */ @Override public long skip(long n) throws IOException { - return skip0(n); + if (isRegularFile()) + return skip0(n); + + return super.skip(n); } private native long skip0(long n) throws IOException; @@ -603,6 +617,18 @@ public class FileInputStream extends InputStream return fc; } + /** + * Determine whether the file is a regular file. + */ + private boolean isRegularFile() { + Boolean isRegularFile = this.isRegularFile; + if (isRegularFile == null) { + this.isRegularFile = isRegularFile = isRegularFile0(fd); + } + return isRegularFile; + } + private native boolean isRegularFile0(FileDescriptor fd); + private static native void initIDs(); static { diff --git a/src/java.base/share/native/libjava/FileInputStream.c b/src/java.base/share/native/libjava/FileInputStream.c index e22499828f5..7db2ec4c208 100644 --- a/src/java.base/share/native/libjava/FileInputStream.c +++ b/src/java.base/share/native/libjava/FileInputStream.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2024, 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 @@ -141,3 +141,9 @@ Java_java_io_FileInputStream_available0(JNIEnv *env, jobject this) { JNU_ThrowIOExceptionWithLastError(env, NULL); return 0; } + +JNIEXPORT jboolean JNICALL +Java_java_io_FileInputStream_isRegularFile0(JNIEnv *env, jobject this, jobject fdo) { + FD fd = getFD(env, this, fis_fd); + return IO_IsRegularFile(env, fd); +} diff --git a/src/java.base/unix/native/libjava/io_util_md.c b/src/java.base/unix/native/libjava/io_util_md.c index 9895ac3b73f..2e81cbd05c2 100644 --- a/src/java.base/unix/native/libjava/io_util_md.c +++ b/src/java.base/unix/native/libjava/io_util_md.c @@ -264,3 +264,13 @@ handleGetLength(FD fd) #endif return sb.st_size; } + +jboolean +handleIsRegularFile(JNIEnv* env, FD fd) +{ + struct stat fbuf; + if (fstat(fd, &fbuf) == -1) + JNU_ThrowIOExceptionWithLastError(env, "fstat failed"); + + return S_ISREG(fbuf.st_mode) ? JNI_TRUE : JNI_FALSE; +} diff --git a/src/java.base/unix/native/libjava/io_util_md.h b/src/java.base/unix/native/libjava/io_util_md.h index 84e97b4ace5..5a8cb8655ef 100644 --- a/src/java.base/unix/native/libjava/io_util_md.h +++ b/src/java.base/unix/native/libjava/io_util_md.h @@ -41,6 +41,7 @@ jint handleAvailable(FD fd, jlong *pbytes); jint handleSetLength(FD fd, jlong length); jlong handleGetLength(FD fd); FD handleOpen(const char *path, int oflag, int mode); +jboolean handleIsRegularFile(JNIEnv* env, FD fd); /* * Functions to get fd from the java.io.FileDescriptor field @@ -66,6 +67,7 @@ FD getFD(JNIEnv *env, jobject cur, jfieldID fid); #define IO_Available handleAvailable #define IO_SetLength handleSetLength #define IO_GetLength handleGetLength +#define IO_IsRegularFile handleIsRegularFile /* * On Solaris, the handle field is unused diff --git a/src/java.base/windows/native/libjava/io_util_md.c b/src/java.base/windows/native/libjava/io_util_md.c index a5f1ced36c5..6a8bd434280 100644 --- a/src/java.base/windows/native/libjava/io_util_md.c +++ b/src/java.base/windows/native/libjava/io_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, 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 @@ -595,3 +595,9 @@ handleGetLength(FD fd) { return -1; } } + +jboolean +handleIsRegularFile(JNIEnv* env, FD fd) +{ + return JNI_TRUE; +} diff --git a/src/java.base/windows/native/libjava/io_util_md.h b/src/java.base/windows/native/libjava/io_util_md.h index d9f239a25c5..82615cc8222 100644 --- a/src/java.base/windows/native/libjava/io_util_md.h +++ b/src/java.base/windows/native/libjava/io_util_md.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, 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 @@ -51,6 +51,7 @@ jint handleAppend(FD fd, const void *buf, jint len); void fileDescriptorClose(JNIEnv *env, jobject this); JNIEXPORT jlong JNICALL handleLseek(FD fd, jlong offset, jint whence); +jboolean handleIsRegularFile(JNIEnv* env, FD fd); /* * Returns an opaque handle to file named by "path". If an error occurs, @@ -82,6 +83,7 @@ FD getFD(JNIEnv *env, jobject cur, jfieldID fid); #define IO_Available handleAvailable #define IO_SetLength handleSetLength #define IO_GetLength handleGetLength +#define IO_IsRegularFile handleIsRegularFile /* * Setting the handle field in Java_java_io_FileDescriptor_set for diff --git a/test/jdk/java/io/FileInputStream/PseudoDevice.java b/test/jdk/java/io/FileInputStream/PseudoDevice.java new file mode 100644 index 00000000000..9f581c3ff81 --- /dev/null +++ b/test/jdk/java/io/FileInputStream/PseudoDevice.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2024, 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. + */ + +/* @test + * @bug 8341666 + * @summary Test of FileInputStream reading from stdin and a named pipe + * @requires os.family != "windows" + * @library .. /test/lib + * @build jdk.test.lib.Platform + * @run junit/othervm --enable-native-access=ALL-UNNAMED PseudoDevice + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import jdk.test.lib.Platform; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import static org.junit.jupiter.api.Assertions.*; + +public class PseudoDevice { + + private static final String PIPE = "pipe"; + private static final File PIPE_FILE = new File(PIPE); + private static final String SENTENCE = + "Rien n'est permis mais tout est possible"; + + private static class mkfifo { + public static final FunctionDescriptor DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_SHORT + ); + + public static final MemorySegment ADDR; + static { + Linker linker = Linker.nativeLinker(); + SymbolLookup stdlib = linker.defaultLookup(); + ADDR = stdlib.find("mkfifo").orElseThrow(); + } + + public static final MethodHandle HANDLE = + Linker.nativeLinker().downcallHandle(ADDR, DESC); + } + + public static int mkfifo(MemorySegment x0, short x1) { + var mh$ = mkfifo.HANDLE; + try { + return (int)mh$.invokeExact(x0, x1); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + + private static Thread createWriteThread() { + Thread t = new Thread( + new Runnable() { + public void run() { + try (FileOutputStream fos = new FileOutputStream(PIPE);) { + fos.write(SENTENCE.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + ); + t.start(); + return t; + } + + @BeforeAll + static void before() throws InterruptedException, IOException { + if (Platform.isWindows()) + return; + + PIPE_FILE.delete(); + try (var newArena = Arena.ofConfined()) { + var addr = newArena.allocateFrom(PIPE); + short mode = 0666; + assertEquals(0, mkfifo(addr, mode)); + } + if (!PIPE_FILE.exists()) + throw new RuntimeException("Failed to create " + PIPE); + } + + @AfterAll + static void after() throws IOException { + if (Platform.isWindows()) + return; + + PIPE_FILE.delete(); + } + + /** + * Tests that new FileInputStream(File).available() does not throw + */ + @Test + @DisabledOnOs(OS.WINDOWS) + void availableStdin() throws IOException { + File stdin = new File("/dev", "stdin"); + if (stdin.exists()) { + try (InputStream s = new FileInputStream(stdin);) { + s.available(); + } + } + } + + /** + * Tests that new FileInputStream(File).skip(0) does not throw + */ + @Test + @DisabledOnOs(OS.WINDOWS) + void skipStdin() throws IOException { + File stdin = new File("/dev", "stdin"); + if (stdin.exists()) { + try (InputStream s = new FileInputStream(stdin);) { + s.skip(0); + } + } + } + + /** + * Tests new FileInputStream(File).readAllBytes(). + */ + @Test + @DisabledOnOs(OS.WINDOWS) + void readAllBytes() throws InterruptedException, IOException { + Thread t = createWriteThread(); + try (InputStream in = new FileInputStream(PIPE)) { + String s = new String(in.readAllBytes()); + System.out.println(s); + assertEquals(SENTENCE, s); + } finally { + t.join(); + } + } + + /** + * Tests new FileInputStream(File).readNBytes(byte[],int,int). + */ + @Test + @DisabledOnOs(OS.WINDOWS) + void readNBytesNoOverride() throws InterruptedException, IOException { + Thread t = createWriteThread(); + try (InputStream in = new FileInputStream(PIPE)) { + final int offset = 11; + final int length = 17; + assert length <= SENTENCE.length(); + byte[] b = new byte[offset + length]; + int n = in.readNBytes(b, offset, length); + String s = new String(b, offset, length); + System.out.println(s); + assertEquals(SENTENCE.substring(0, length), s); + } finally { + t.join(); + } + } + + /** + * Tests new FileInputStream(File).readNBytes(int). + */ + @Test + @DisabledOnOs(OS.WINDOWS) + void readNBytesOverride() throws InterruptedException, IOException { + Thread t = createWriteThread(); + try (InputStream in = new FileInputStream(PIPE)) { + final int length = 17; + assert length <= SENTENCE.length(); + byte[] b = in.readNBytes(length); + String s = new String(b); + System.out.println(s); + assertEquals(SENTENCE.substring(0, length), s); + } finally { + t.join(); + } + } +}