Skip to content

Commit 6e23b43

Browse files
author
Brian Burkhalter
committed
8293502: (fc) FileChannel::transfer methods fail to copy /proc files on Linux
Reviewed-by: alanb
1 parent 1f9ff41 commit 6e23b43

File tree

2 files changed

+206
-14
lines changed

2 files changed

+206
-14
lines changed

src/java.base/share/classes/sun/nio/ch/FileChannelImpl.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -761,23 +761,33 @@ public long transferTo(long position, long count,
761761
throw new NonWritableChannelException();
762762
if ((position < 0) || (count < 0))
763763
throw new IllegalArgumentException();
764-
long sz = size();
764+
final long sz = size();
765765
if (position > sz)
766766
return 0;
767767

768-
if ((sz - position) < count)
769-
count = sz - position;
770-
771-
// Attempt a direct transfer, if the kernel supports it, limiting
772-
// the number of bytes according to which platform
773-
int icount = (int)Math.min(count, MAX_DIRECT_TRANSFER_SIZE);
774-
long n;
775-
if ((n = transferToDirectly(position, icount, target)) >= 0)
776-
return n;
768+
// Now position <= sz so remaining >= 0 and
769+
// remaining == 0 if and only if sz == 0
770+
long remaining = sz - position;
771+
772+
// Adjust count only if remaining > 0, i.e.,
773+
// sz > position which means sz > 0
774+
if (remaining > 0 && remaining < count)
775+
count = remaining;
776+
777+
// System calls supporting fast transfers might not work on files
778+
// which advertise zero size such as those in Linux /proc
779+
if (sz > 0) {
780+
// Attempt a direct transfer, if the kernel supports it, limiting
781+
// the number of bytes according to which platform
782+
int icount = (int)Math.min(count, MAX_DIRECT_TRANSFER_SIZE);
783+
long n;
784+
if ((n = transferToDirectly(position, icount, target)) >= 0)
785+
return n;
777786

778-
// Attempt a mapped transfer, but only to trusted channel types
779-
if ((n = transferToTrustedChannel(position, count, target)) >= 0)
780-
return n;
787+
// Attempt a mapped transfer, but only to trusted channel types
788+
if ((n = transferToTrustedChannel(position, count, target)) >= 0)
789+
return n;
790+
}
781791

782792
// Slow path for untrusted targets
783793
return transferToArbitraryChannel(position, count, target);
@@ -925,14 +935,18 @@ public long transferFrom(ReadableByteChannel src,
925935
ensureOpen();
926936
if (!src.isOpen())
927937
throw new ClosedChannelException();
938+
if (src instanceof FileChannelImpl fci && !fci.readable)
939+
throw new NonReadableChannelException();
928940
if (!writable)
929941
throw new NonWritableChannelException();
930942
if ((position < 0) || (count < 0))
931943
throw new IllegalArgumentException();
932944
if (position > size())
933945
return 0;
934946

935-
if (src instanceof FileChannelImpl fci) {
947+
// System calls supporting fast transfers might not work on files
948+
// which advertise zero size such as those in Linux /proc
949+
if (src instanceof FileChannelImpl fci && fci.size() > 0) {
936950
long n;
937951
if ((n = transferFromDirectly(fci, position, count)) >= 0)
938952
return n;
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Copyright (c) 2022, 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+
import java.io.IOException;
25+
import java.io.FileInputStream;
26+
import java.io.FileOutputStream;
27+
import java.io.InputStream;
28+
import java.io.OutputStream;
29+
import java.io.UncheckedIOException;
30+
import java.nio.channels.FileChannel;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.function.ToLongBiFunction;
36+
import static java.nio.file.StandardOpenOption.*;
37+
38+
import org.testng.Assert;
39+
import org.testng.annotations.AfterTest;
40+
import org.testng.annotations.BeforeTest;
41+
import org.testng.annotations.DataProvider;
42+
import org.testng.annotations.Test;
43+
44+
/*
45+
* @test
46+
* @bug 8293502
47+
* @requires (os.family == "linux")
48+
* @summary Ensure that copying from a file in /proc works
49+
* @run testng/othervm CopyProcFile
50+
*/
51+
public class CopyProcFile {
52+
static final String SOURCE = "/proc/cpuinfo";
53+
static final String BUFFERED_COPY = "bufferedCopy";
54+
static final String TARGET = "target";
55+
56+
static final int BUF_SIZE = 8192;
57+
58+
static long theSize;
59+
60+
// copy src to dst via Java buffers
61+
static long bufferedCopy(String src, String dst) {
62+
try (InputStream in = new FileInputStream(src);
63+
OutputStream out = new FileOutputStream(dst)) {
64+
byte[] b = new byte[BUF_SIZE];
65+
long total = 0;
66+
int n;
67+
while ((n = in.read(b)) > 0) {
68+
out.write(b, 0, n);
69+
total += n;
70+
}
71+
return total;
72+
} catch (IOException e) {
73+
throw new UncheckedIOException(e);
74+
}
75+
}
76+
77+
// copy src to dst using Files::copy
78+
static long copy(String src, String dst) {
79+
try {
80+
Path target = Files.copy(Path.of(src), Path.of(dst));
81+
return Files.size(target);
82+
} catch (IOException e) {
83+
throw new UncheckedIOException(e);
84+
}
85+
}
86+
87+
// copy src to dst using InputStream::transferTo
88+
static long transferToIO(String src, String dst) {
89+
try (InputStream in = new FileInputStream(src);
90+
OutputStream out = new FileOutputStream(dst)) {
91+
return in.transferTo(out);
92+
} catch (IOException e) {
93+
throw new UncheckedIOException(e);
94+
}
95+
}
96+
97+
// copy src to dst using FileChannel::transferTo
98+
static long transferToNIO(String src, String dst) {
99+
try (FileChannel fci = FileChannel.open(Path.of(src), READ);
100+
FileChannel fco = FileChannel.open(Path.of(dst), CREATE_NEW, WRITE);) {
101+
return fci.transferTo(0, Long.MAX_VALUE, fco);
102+
} catch (IOException e) {
103+
throw new UncheckedIOException(e);
104+
}
105+
}
106+
107+
// copy src to dst using FileChannel::transferFrom
108+
static long transferFrom(String src, String dst) {
109+
try (FileChannel fci = FileChannel.open(Path.of(src), READ);
110+
FileChannel fco = FileChannel.open(Path.of(dst), CREATE_NEW, WRITE);) {
111+
return fco.transferFrom(fci, 0, Long.MAX_VALUE);
112+
} catch (IOException e) {
113+
throw new UncheckedIOException(e);
114+
}
115+
}
116+
117+
@BeforeTest(alwaysRun=true)
118+
public void createBufferedCopy() {
119+
System.out.printf("Using source file \"%s\"%n", SOURCE);
120+
try {
121+
theSize = bufferedCopy(SOURCE, BUFFERED_COPY);
122+
System.out.printf("Copied %d bytes from %s%n", theSize, SOURCE);
123+
if (Files.mismatch(Path.of(BUFFERED_COPY), Path.of(SOURCE)) != -1)
124+
throw new RuntimeException("Copy does not match source");
125+
} catch (Exception e) {
126+
try {
127+
Files.delete(Path.of(BUFFERED_COPY));
128+
} catch (IOException ignore) {}
129+
}
130+
}
131+
132+
@AfterTest(alwaysRun=true)
133+
public void deleteBufferedCopy() {
134+
try {
135+
Files.delete(Path.of(BUFFERED_COPY));
136+
} catch (IOException ignore) {}
137+
}
138+
139+
static class FHolder {
140+
ToLongBiFunction<String,String> f;
141+
142+
FHolder(ToLongBiFunction<String,String> f) {
143+
this.f = f;
144+
}
145+
146+
long apply(String src, String dst) {
147+
return f.applyAsLong(src, dst);
148+
}
149+
}
150+
151+
@DataProvider
152+
static Object[][] functions() throws IOException {
153+
List<Object[]> funcs = new ArrayList<>();
154+
funcs.add(new Object[] {new FHolder((s, d) -> copy(s, d))});
155+
funcs.add(new Object[] {new FHolder((s, d) -> transferToIO(s, d))});
156+
funcs.add(new Object[] {new FHolder((s, d) -> transferToNIO(s, d))});
157+
funcs.add(new Object[] {new FHolder((s, d) -> transferFrom(s, d))});
158+
return funcs.toArray(Object[][]::new);
159+
}
160+
161+
@Test(dataProvider = "functions")
162+
public static void testCopyAndTransfer(FHolder f) throws IOException {
163+
try {
164+
long size = f.apply(SOURCE, TARGET);
165+
if (size != theSize)
166+
throw new RuntimeException("Size: expected " + theSize +
167+
"; actual: " + size);
168+
long mismatch = Files.mismatch(Path.of(BUFFERED_COPY),
169+
Path.of(TARGET));
170+
if (mismatch != -1)
171+
throw new RuntimeException("Target does not match copy");
172+
} finally {
173+
try {
174+
Files.delete(Path.of(TARGET));
175+
} catch (IOException ignore) {}
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)