Skip to content

Commit 1341b81

Browse files
author
Brian Burkhalter
committed
8341666: FileInputStream doesn't support readAllBytes() or readNBytes(int) on pseudo devices
Reviewed-by: alanb
1 parent 52382e2 commit 1341b81

File tree

7 files changed

+267
-5
lines changed

7 files changed

+267
-5
lines changed

src/java.base/share/classes/java/io/FileInputStream.java

+28-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Arrays;
3030
import jdk.internal.util.ArraysSupport;
3131
import jdk.internal.event.FileReadEvent;
32+
import jdk.internal.vm.annotation.Stable;
3233
import sun.nio.ch.FileChannelImpl;
3334

3435
/**
@@ -81,6 +82,10 @@ public class FileInputStream extends InputStream
8182

8283
private volatile boolean closed;
8384

85+
// This field indicates whether the file is a regular file as some
86+
// operations need the current position which requires seeking
87+
private @Stable Boolean isRegularFile;
88+
8489
/**
8590
* Creates a {@code FileInputStream} to read from an existing file
8691
* named by the path name {@code name}.
@@ -331,6 +336,9 @@ public int read(byte[] b, int off, int len) throws IOException {
331336

332337
@Override
333338
public byte[] readAllBytes() throws IOException {
339+
if (!isRegularFile())
340+
return super.readAllBytes();
341+
334342
long length = length();
335343
long position = position();
336344
long size = length - position;
@@ -382,6 +390,9 @@ public byte[] readNBytes(int len) throws IOException {
382390
if (len == 0)
383391
return new byte[0];
384392

393+
if (!isRegularFile())
394+
return super.readNBytes(len);
395+
385396
long length = length();
386397
long position = position();
387398
long size = length - position;
@@ -418,7 +429,7 @@ public byte[] readNBytes(int len) throws IOException {
418429
@Override
419430
public long transferTo(OutputStream out) throws IOException {
420431
long transferred = 0L;
421-
if (out instanceof FileOutputStream fos) {
432+
if (out instanceof FileOutputStream fos && isRegularFile()) {
422433
FileChannel fc = getChannel();
423434
long pos = fc.position();
424435
transferred = fc.transferTo(pos, Long.MAX_VALUE, fos.getChannel());
@@ -471,7 +482,10 @@ private long position() throws IOException {
471482
*/
472483
@Override
473484
public long skip(long n) throws IOException {
474-
return skip0(n);
485+
if (isRegularFile())
486+
return skip0(n);
487+
488+
return super.skip(n);
475489
}
476490

477491
private native long skip0(long n) throws IOException;
@@ -603,6 +617,18 @@ public FileChannel getChannel() {
603617
return fc;
604618
}
605619

620+
/**
621+
* Determine whether the file is a regular file.
622+
*/
623+
private boolean isRegularFile() {
624+
Boolean isRegularFile = this.isRegularFile;
625+
if (isRegularFile == null) {
626+
this.isRegularFile = isRegularFile = isRegularFile0(fd);
627+
}
628+
return isRegularFile;
629+
}
630+
private native boolean isRegularFile0(FileDescriptor fd);
631+
606632
private static native void initIDs();
607633

608634
static {

src/java.base/share/native/libjava/FileInputStream.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* 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) {
141141
JNU_ThrowIOExceptionWithLastError(env, NULL);
142142
return 0;
143143
}
144+
145+
JNIEXPORT jboolean JNICALL
146+
Java_java_io_FileInputStream_isRegularFile0(JNIEnv *env, jobject this, jobject fdo) {
147+
FD fd = getFD(env, this, fis_fd);
148+
return IO_IsRegularFile(env, fd);
149+
}

src/java.base/unix/native/libjava/io_util_md.c

+10
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,13 @@ handleGetLength(FD fd)
264264
#endif
265265
return sb.st_size;
266266
}
267+
268+
jboolean
269+
handleIsRegularFile(JNIEnv* env, FD fd)
270+
{
271+
struct stat fbuf;
272+
if (fstat(fd, &fbuf) == -1)
273+
JNU_ThrowIOExceptionWithLastError(env, "fstat failed");
274+
275+
return S_ISREG(fbuf.st_mode) ? JNI_TRUE : JNI_FALSE;
276+
}

src/java.base/unix/native/libjava/io_util_md.h

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ jint handleAvailable(FD fd, jlong *pbytes);
4141
jint handleSetLength(FD fd, jlong length);
4242
jlong handleGetLength(FD fd);
4343
FD handleOpen(const char *path, int oflag, int mode);
44+
jboolean handleIsRegularFile(JNIEnv* env, FD fd);
4445

4546
/*
4647
* Functions to get fd from the java.io.FileDescriptor field
@@ -66,6 +67,7 @@ FD getFD(JNIEnv *env, jobject cur, jfieldID fid);
6667
#define IO_Available handleAvailable
6768
#define IO_SetLength handleSetLength
6869
#define IO_GetLength handleGetLength
70+
#define IO_IsRegularFile handleIsRegularFile
6971

7072
/*
7173
* On Solaris, the handle field is unused

src/java.base/windows/native/libjava/io_util_md.c

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -595,3 +595,9 @@ handleGetLength(FD fd) {
595595
return -1;
596596
}
597597
}
598+
599+
jboolean
600+
handleIsRegularFile(JNIEnv* env, FD fd)
601+
{
602+
return JNI_TRUE;
603+
}

src/java.base/windows/native/libjava/io_util_md.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* 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);
5151
void fileDescriptorClose(JNIEnv *env, jobject this);
5252
JNIEXPORT jlong JNICALL
5353
handleLseek(FD fd, jlong offset, jint whence);
54+
jboolean handleIsRegularFile(JNIEnv* env, FD fd);
5455

5556
/*
5657
* 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);
8283
#define IO_Available handleAvailable
8384
#define IO_SetLength handleSetLength
8485
#define IO_GetLength handleGetLength
86+
#define IO_IsRegularFile handleIsRegularFile
8587

8688
/*
8789
* Setting the handle field in Java_java_io_FileDescriptor_set for
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* Copyright (c) 2024, 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/* @test
25+
* @bug 8341666
26+
* @summary Test of FileInputStream reading from stdin and a named pipe
27+
* @requires os.family != "windows"
28+
* @library .. /test/lib
29+
* @build jdk.test.lib.Platform
30+
* @run junit/othervm --enable-native-access=ALL-UNNAMED PseudoDevice
31+
*/
32+
33+
import java.io.File;
34+
import java.io.FileInputStream;
35+
import java.io.FileOutputStream;
36+
import java.io.InputStream;
37+
import java.io.IOException;
38+
import java.lang.foreign.Arena;
39+
import java.lang.foreign.FunctionDescriptor;
40+
import java.lang.foreign.Linker;
41+
import java.lang.foreign.MemorySegment;
42+
import java.lang.foreign.SymbolLookup;
43+
import java.lang.foreign.ValueLayout;
44+
import java.lang.invoke.MethodHandle;
45+
import jdk.test.lib.Platform;
46+
47+
import org.junit.jupiter.api.AfterAll;
48+
import org.junit.jupiter.api.BeforeAll;
49+
import org.junit.jupiter.api.Test;
50+
import org.junit.jupiter.api.condition.DisabledOnOs;
51+
import org.junit.jupiter.api.condition.OS;
52+
import static org.junit.jupiter.api.Assertions.*;
53+
54+
public class PseudoDevice {
55+
56+
private static final String PIPE = "pipe";
57+
private static final File PIPE_FILE = new File(PIPE);
58+
private static final String SENTENCE =
59+
"Rien n'est permis mais tout est possible";
60+
61+
private static class mkfifo {
62+
public static final FunctionDescriptor DESC = FunctionDescriptor.of(
63+
ValueLayout.JAVA_INT,
64+
ValueLayout.ADDRESS,
65+
ValueLayout.JAVA_SHORT
66+
);
67+
68+
public static final MemorySegment ADDR;
69+
static {
70+
Linker linker = Linker.nativeLinker();
71+
SymbolLookup stdlib = linker.defaultLookup();
72+
ADDR = stdlib.find("mkfifo").orElseThrow();
73+
}
74+
75+
public static final MethodHandle HANDLE =
76+
Linker.nativeLinker().downcallHandle(ADDR, DESC);
77+
}
78+
79+
public static int mkfifo(MemorySegment x0, short x1) {
80+
var mh$ = mkfifo.HANDLE;
81+
try {
82+
return (int)mh$.invokeExact(x0, x1);
83+
} catch (Throwable ex$) {
84+
throw new AssertionError("should not reach here", ex$);
85+
}
86+
}
87+
88+
private static Thread createWriteThread() {
89+
Thread t = new Thread(
90+
new Runnable() {
91+
public void run() {
92+
try (FileOutputStream fos = new FileOutputStream(PIPE);) {
93+
fos.write(SENTENCE.getBytes());
94+
} catch (IOException e) {
95+
throw new RuntimeException(e);
96+
}
97+
}
98+
}
99+
);
100+
t.start();
101+
return t;
102+
}
103+
104+
@BeforeAll
105+
static void before() throws InterruptedException, IOException {
106+
if (Platform.isWindows())
107+
return;
108+
109+
PIPE_FILE.delete();
110+
try (var newArena = Arena.ofConfined()) {
111+
var addr = newArena.allocateFrom(PIPE);
112+
short mode = 0666;
113+
assertEquals(0, mkfifo(addr, mode));
114+
}
115+
if (!PIPE_FILE.exists())
116+
throw new RuntimeException("Failed to create " + PIPE);
117+
}
118+
119+
@AfterAll
120+
static void after() throws IOException {
121+
if (Platform.isWindows())
122+
return;
123+
124+
PIPE_FILE.delete();
125+
}
126+
127+
/**
128+
* Tests that new FileInputStream(File).available() does not throw
129+
*/
130+
@Test
131+
@DisabledOnOs(OS.WINDOWS)
132+
void availableStdin() throws IOException {
133+
File stdin = new File("/dev", "stdin");
134+
if (stdin.exists()) {
135+
try (InputStream s = new FileInputStream(stdin);) {
136+
s.available();
137+
}
138+
}
139+
}
140+
141+
/**
142+
* Tests that new FileInputStream(File).skip(0) does not throw
143+
*/
144+
@Test
145+
@DisabledOnOs(OS.WINDOWS)
146+
void skipStdin() throws IOException {
147+
File stdin = new File("/dev", "stdin");
148+
if (stdin.exists()) {
149+
try (InputStream s = new FileInputStream(stdin);) {
150+
s.skip(0);
151+
}
152+
}
153+
}
154+
155+
/**
156+
* Tests new FileInputStream(File).readAllBytes().
157+
*/
158+
@Test
159+
@DisabledOnOs(OS.WINDOWS)
160+
void readAllBytes() throws InterruptedException, IOException {
161+
Thread t = createWriteThread();
162+
try (InputStream in = new FileInputStream(PIPE)) {
163+
String s = new String(in.readAllBytes());
164+
System.out.println(s);
165+
assertEquals(SENTENCE, s);
166+
} finally {
167+
t.join();
168+
}
169+
}
170+
171+
/**
172+
* Tests new FileInputStream(File).readNBytes(byte[],int,int).
173+
*/
174+
@Test
175+
@DisabledOnOs(OS.WINDOWS)
176+
void readNBytesNoOverride() throws InterruptedException, IOException {
177+
Thread t = createWriteThread();
178+
try (InputStream in = new FileInputStream(PIPE)) {
179+
final int offset = 11;
180+
final int length = 17;
181+
assert length <= SENTENCE.length();
182+
byte[] b = new byte[offset + length];
183+
int n = in.readNBytes(b, offset, length);
184+
String s = new String(b, offset, length);
185+
System.out.println(s);
186+
assertEquals(SENTENCE.substring(0, length), s);
187+
} finally {
188+
t.join();
189+
}
190+
}
191+
192+
/**
193+
* Tests new FileInputStream(File).readNBytes(int).
194+
*/
195+
@Test
196+
@DisabledOnOs(OS.WINDOWS)
197+
void readNBytesOverride() throws InterruptedException, IOException {
198+
Thread t = createWriteThread();
199+
try (InputStream in = new FileInputStream(PIPE)) {
200+
final int length = 17;
201+
assert length <= SENTENCE.length();
202+
byte[] b = in.readNBytes(length);
203+
String s = new String(b);
204+
System.out.println(s);
205+
assertEquals(SENTENCE.substring(0, length), s);
206+
} finally {
207+
t.join();
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)