8233451: (fs) Files.newInputStream() cannot be used with character special files

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2024-10-23 18:53:30 +00:00
parent 002de86081
commit de92fe3757
8 changed files with 415 additions and 31 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2023, 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
@ -38,6 +38,7 @@ import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.Objects;
import jdk.internal.util.ArraysSupport;
import jdk.internal.vm.annotation.Stable;
/**
* An InputStream that reads bytes from a channel.
@ -53,6 +54,10 @@ class ChannelInputStream extends InputStream {
private byte[] bs; // Invoker's previous array
private byte[] b1;
// if isOther is true, then the file being read is not a regular file,
// nor a directory, nor a symbolic link, hence possibly not seekable
private @Stable Boolean isOther;
/**
* Initialize a ChannelInputStream that reads from the given channel.
*/
@ -60,6 +65,17 @@ class ChannelInputStream extends InputStream {
this.ch = ch;
}
private boolean isOther() throws IOException {
Boolean isOther = this.isOther;
if (isOther == null) {
if (ch instanceof FileChannelImpl fci)
this.isOther = isOther = fci.isOther();
else
this.isOther = isOther = Boolean.FALSE;
}
return isOther;
}
/**
* Reads a sequence of bytes from the channel into the given buffer.
*/
@ -105,7 +121,8 @@ class ChannelInputStream extends InputStream {
@Override
public byte[] readAllBytes() throws IOException {
if (!(ch instanceof SeekableByteChannel sbc))
if (!(ch instanceof SeekableByteChannel sbc) ||
(ch instanceof FileChannelImpl fci && isOther()))
return super.readAllBytes();
long length = sbc.size();
@ -156,7 +173,8 @@ class ChannelInputStream extends InputStream {
if (len == 0)
return new byte[0];
if (!(ch instanceof SeekableByteChannel sbc))
if (!(ch instanceof SeekableByteChannel sbc) ||
(ch instanceof FileChannelImpl fci && isOther()))
return super.readNBytes(len);
long length = sbc.size();
@ -192,7 +210,9 @@ class ChannelInputStream extends InputStream {
@Override
public int available() throws IOException {
// special case where the channel is to a file
if (ch instanceof SeekableByteChannel sbc) {
if (ch instanceof FileChannelImpl fci) {
return fci.available();
} else if (ch instanceof SeekableByteChannel sbc) {
long rem = Math.max(0, sbc.size() - sbc.position());
return (rem > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)rem;
}
@ -202,7 +222,8 @@ class ChannelInputStream extends InputStream {
@Override
public synchronized long skip(long n) throws IOException {
// special case where the channel is to a file
if (ch instanceof SeekableByteChannel sbc) {
if (ch instanceof SeekableByteChannel sbc &&
!(ch instanceof FileChannelImpl fci && isOther())) {
long pos = sbc.position();
long newPos;
if (n > 0) {
@ -224,7 +245,8 @@ class ChannelInputStream extends InputStream {
public long transferTo(OutputStream out) throws IOException {
Objects.requireNonNull(out, "out");
if (ch instanceof FileChannel fc) {
if (ch instanceof FileChannel fc &&
!(fc instanceof FileChannelImpl fci && isOther())) {
// FileChannel -> SocketChannel
if (out instanceof SocketOutputStream sos) {
SocketChannelImpl sc = sos.channel();

View File

@ -454,6 +454,7 @@ public class FileChannelImpl
}
return bytesWritten;
}
// -- Other operations --
@Override
@ -529,6 +530,49 @@ public class FileChannelImpl
}
}
/**
* Returns an estimate of the number of remaining bytes that can be read
* from this channel without blocking.
*/
int available() throws IOException {
ensureOpen();
synchronized (positionLock) {
int a = -1;
int ti = -1;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return -1;
a = nd.available(fd);
} finally {
threads.remove(ti);
endBlocking(a > -1);
}
return a;
}
}
/**
* Tells whether the channel represents something other than a regular
* file, directory, or symbolic link.
*/
boolean isOther() throws IOException {
ensureOpen();
int ti = -1;
Boolean isOther = null;
try {
beginBlocking();
ti = threads.add();
if (!isOpen())
return false;
return isOther = nd.isOther(fd);
} finally {
threads.remove(ti);
endBlocking(isOther != null);
}
}
@Override
public FileChannel truncate(long newSize) throws IOException {
ensureOpen();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 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
@ -49,6 +49,10 @@ abstract class FileDispatcher extends NativeDispatcher {
abstract long size(FileDescriptor fd) throws IOException;
abstract int available(FileDescriptor fd) throws IOException;
abstract boolean isOther(FileDescriptor fd) throws IOException;
abstract int lock(FileDescriptor fd, boolean blocking, long pos, long size,
boolean shared) throws IOException;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -93,6 +93,14 @@ class UnixFileDispatcherImpl extends FileDispatcher {
return size0(fd);
}
int available(FileDescriptor fd) throws IOException {
return available0(fd);
}
boolean isOther(FileDescriptor fd) throws IOException {
return isOther0(fd);
}
int lock(FileDescriptor fd, boolean blocking, long pos, long size,
boolean shared) throws IOException
{
@ -196,6 +204,10 @@ class UnixFileDispatcherImpl extends FileDispatcher {
static native long size0(FileDescriptor fd) throws IOException;
static native int available0(FileDescriptor fd) throws IOException;
static native boolean isOther0(FileDescriptor fd) throws IOException;
static native int lock0(FileDescriptor fd, boolean blocking, long pos,
long size, boolean shared) throws IOException;

View File

@ -23,6 +23,7 @@
* questions.
*/
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/uio.h>
#include <sys/stat.h>
@ -41,8 +42,10 @@
#include "nio.h"
#include "nio_util.h"
#include "sun_nio_ch_UnixFileDispatcherImpl.h"
#include "java_lang_Integer.h"
#include "java_lang_Long.h"
#include <assert.h>
#include "io_util_md.h"
#if defined(_AIX)
#define statvfs statvfs64
@ -178,6 +181,57 @@ Java_sun_nio_ch_UnixFileDispatcherImpl_size0(JNIEnv *env, jobject this, jobject
return fbuf.st_size;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_UnixFileDispatcherImpl_available0(JNIEnv *env, jobject this, jobject fdo)
{
jint fd = fdval(env, fdo);
struct stat fbuf;
jlong size = -1;
if (fstat(fd, &fbuf) != -1) {
int mode = fbuf.st_mode;
if (S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) {
int n = ioctl(fd, FIONREAD, &n);
if (n >= 0) {
return n;
}
} else if (S_ISREG(mode)) {
size = fbuf.st_size;
}
}
jlong position;
if ((position = lseek(fd, 0, SEEK_CUR)) == -1) {
return 0;
}
if (size < position) {
if ((size = lseek(fd, 0, SEEK_END)) == -1)
return 0;
else if (lseek(fd, position, SEEK_SET) == -1)
return 0;
}
jlong available = size - position;
return available > java_lang_Integer_MAX_VALUE ?
java_lang_Integer_MAX_VALUE : (jint)available;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_UnixFileDispatcherImpl_isOther0(JNIEnv *env, jobject this, jobject fdo)
{
jint fd = fdval(env, fdo);
struct stat fbuf;
if (fstat(fd, &fbuf) == -1)
handle(env, -1, "isOther failed");
if (S_ISREG(fbuf.st_mode) || S_ISDIR(fbuf.st_mode) || S_ISLNK(fbuf.st_mode))
return JNI_FALSE;
return JNI_TRUE;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_UnixFileDispatcherImpl_lock0(JNIEnv *env, jobject this, jobject fdo,
jboolean block, jlong pos, jlong size,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -101,6 +101,14 @@ class FileDispatcherImpl extends FileDispatcher {
return size0(fd);
}
int available(FileDescriptor fd) throws IOException {
return available0(fd);
}
boolean isOther(FileDescriptor fd) throws IOException {
return isOther0(fd);
}
int lock(FileDescriptor fd, boolean blocking, long pos, long size,
boolean shared) throws IOException
{
@ -223,6 +231,10 @@ class FileDispatcherImpl extends FileDispatcher {
static native long size0(FileDescriptor fd) throws IOException;
static native int available0(FileDescriptor fd) throws IOException;
static native boolean isOther0(FileDescriptor fd) throws IOException;
static native int lock0(FileDescriptor fd, boolean blocking, long pos,
long size, boolean shared) throws IOException;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 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
@ -24,6 +24,7 @@
*/
#include <windows.h>
#include <winioctl.h>
#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
@ -33,6 +34,7 @@
#include "nio_util.h"
#include "java_lang_Integer.h"
#include "sun_nio_ch_FileDispatcherImpl.h"
#include "io_util_md.h"
#include <Mswsock.h> // Requires Mswsock.lib
@ -392,6 +394,75 @@ Java_sun_nio_ch_FileDispatcherImpl_size0(JNIEnv *env, jobject this, jobject fdo)
return (jlong)size.QuadPart;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_available0(JNIEnv *env, jobject this, jobject fdo)
{
HANDLE handle = (HANDLE)(handleval(env, fdo));
DWORD type = GetFileType(handle);
jlong available = 0;
// Calculate the number of bytes available for a regular file,
// and return the default (zero) for other types.
if (type == FILE_TYPE_DISK) {
jlong current, end;
LARGE_INTEGER distance, pos, filesize;
distance.QuadPart = 0;
if (SetFilePointerEx(handle, distance, &pos, FILE_CURRENT) == 0) {
JNU_ThrowIOExceptionWithLastError(env, "Available failed");
return IOS_THROWN;
}
current = (jlong)pos.QuadPart;
if (GetFileSizeEx(handle, &filesize) == 0) {
JNU_ThrowIOExceptionWithLastError(env, "Available failed");
return IOS_THROWN;
}
end = (jlong)filesize.QuadPart;
available = end - current;
if (available > java_lang_Integer_MAX_VALUE) {
available = java_lang_Integer_MAX_VALUE;
} else if (available < 0) {
available = 0;
}
}
return (jint)available;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_FileDispatcherImpl_isOther0(JNIEnv *env, jobject this, jobject fdo)
{
HANDLE handle = (HANDLE)(handleval(env, fdo));
BY_HANDLE_FILE_INFORMATION finfo;
if (!GetFileInformationByHandle(handle, &finfo))
JNU_ThrowIOExceptionWithLastError(env, "isOther failed");
DWORD fattr = finfo.dwFileAttributes;
if ((fattr & FILE_ATTRIBUTE_DEVICE) != 0)
return (jboolean)JNI_TRUE;
if ((fattr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
int size = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
void* lpOutBuffer = (void*)malloc(size*sizeof(char));
if (lpOutBuffer == NULL)
JNU_ThrowOutOfMemoryError(env, "isOther failed");
DWORD bytesReturned;
if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0,
lpOutBuffer, (DWORD)size, &bytesReturned, NULL)) {
free(lpOutBuffer);
JNU_ThrowIOExceptionWithLastError(env, "isOther failed");
}
ULONG reparseTag = (*((PULONG)lpOutBuffer));
free(lpOutBuffer);
return reparseTag == IO_REPARSE_TAG_SYMLINK ?
(jboolean)JNI_FALSE : (jboolean)JNI_TRUE;
}
return (jboolean)JNI_FALSE;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_FileDispatcherImpl_lock0(JNIEnv *env, jobject this, jobject fdo,
jboolean block, jlong pos, jlong size,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 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
@ -22,37 +22,123 @@
*/
/* @test
* @bug 8227609
* @bug 8227609 8233451
* @summary Test of InputStream and OutputStream created by java.nio.file.Files
* @library ..
* @library .. /test/lib
* @build jdk.test.lib.Platform
* @run junit/othervm --enable-native-access=ALL-UNNAMED InputStreamTest
*/
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.ClosedChannelException;
import java.nio.file.*;
import static java.nio.file.Files.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.IOException;
import java.util.*;
import java.io.OutputStream;
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 java.nio.channels.ClosedChannelException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
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 InputStreamTest {
public static void main(String[] args) throws IOException {
Path dir = TestUtil.createTemporaryDirectory();
try {
testSkip(dir);
} finally {
TestUtil.removeAll(dir);
private static final String PIPE = "pipe";
private static final Path PIPE_PATH = Path.of(PIPE);
private static final String SENTENCE =
"Tout est permis mais rien nest possible";
private static Path TMPDIR;
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 {
TMPDIR = TestUtil.createTemporaryDirectory();
if (Platform.isWindows())
return;
Files.deleteIfExists(PIPE_PATH);
try (var newArena = Arena.ofConfined()) {
var addr = newArena.allocateFrom(PIPE);
short mode = 0666;
assertEquals(0, mkfifo(addr, mode));
}
if (Files.notExists(PIPE_PATH))
throw new RuntimeException("Failed to create " + PIPE);
}
@AfterAll
static void after() throws IOException {
TestUtil.removeAll(TMPDIR);
if (Platform.isWindows())
return;
Files.deleteIfExists(PIPE_PATH);
}
/**
* Tests Files.newInputStream(Path).skip().
*/
static void testSkip(Path tmpdir) throws IOException {
Path file = createFile(tmpdir.resolve("foo"));
@Test
void skip() throws IOException {
Path file = Files.createFile(TMPDIR.resolve("foo"));
try (OutputStream out = Files.newOutputStream(file)) {
final int size = 512;
byte[] blah = new byte[size];
@ -123,8 +209,87 @@ public class InputStreamTest {
}
}
static void assertTrue(boolean okay) {
if (!okay)
throw new RuntimeException("Assertion Failed");
/**
* Tests that Files.newInputStream(Path).available() does not throw
*/
@Test
@DisabledOnOs(OS.WINDOWS)
void availableStdin() throws IOException {
Path stdin = Path.of("/dev", "stdin");
if (Files.exists(stdin)) {
try (InputStream s = Files.newInputStream(stdin);) {
s.available();
}
}
}
/**
* Tests that Files.newInputStream(Path).skip(0) does not throw
*/
@Test
@DisabledOnOs(OS.WINDOWS)
void skipStdin() throws IOException {
Path stdin = Path.of("/dev", "stdin");
if (Files.exists(stdin)) {
try (InputStream s = Files.newInputStream(stdin);) {
s.skip(0);
}
}
}
/**
* Tests Files.newInputStream(Path).readAllBytes().
*/
@Test
@DisabledOnOs(OS.WINDOWS)
void readAllBytes() throws InterruptedException, IOException {
Thread t = createWriteThread();
try (InputStream in = Files.newInputStream(Path.of(PIPE))) {
String s = new String(in.readAllBytes());
System.out.println(s);
assertEquals(SENTENCE, s);
} finally {
t.join();
}
}
/**
* Tests Files.newInputStream(Path).readNBytes(byte[],int,int).
*/
@Test
@DisabledOnOs(OS.WINDOWS)
void readNBytesNoOverride() throws InterruptedException, IOException {
Thread t = createWriteThread();
try (InputStream in = Files.newInputStream(Path.of(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 Files.newInputStream(Path).readNBytes(int).
*/
@Test
@DisabledOnOs(OS.WINDOWS)
void readNBytesOverride() throws InterruptedException, IOException {
Thread t = createWriteThread();
try (InputStream in = Files.newInputStream(Path.of(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();
}
}
}