Skip to content

Commit 3bc0f7c

Browse files
committed
[libc++] support special linux files in fs::copy_file
Virtual linux filesystems such as /proc and /sys may contain files that appear as zero length, but do contain data. These require a traditional userspace read + write loop. Signed-off-by: Jannik Glückert <[email protected]>
1 parent 63d5003 commit 3bc0f7c

File tree

2 files changed

+103
-34
lines changed

2 files changed

+103
-34
lines changed

libcxx/src/filesystem/operations.cpp

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,17 @@
4545
# include <copyfile.h>
4646
# define _LIBCPP_FILESYSTEM_USE_COPYFILE
4747
#else
48-
# include <fstream>
4948
# define _LIBCPP_FILESYSTEM_USE_FSTREAM
5049
#endif
5150

51+
// sendfile and copy_file_range need to fall back
52+
// to the fstream implementation for special files
53+
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE) || defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE) || \
54+
defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
55+
# include <fstream>
56+
# define _LIBCPP_FILESYSTEM_NEED_FSTREAM
57+
#endif
58+
5259
#if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB)
5360
# pragma comment(lib, "rt")
5461
#endif
@@ -179,6 +186,42 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
179186
namespace detail {
180187
namespace {
181188

189+
#if defined(_LIBCPP_FILESYSTEM_NEED_FSTREAM)
190+
bool copy_file_impl_fstream(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
191+
ifstream in;
192+
in.__open(read_fd.fd, ios::binary);
193+
if (!in.is_open()) {
194+
// This assumes that __open didn't reset the error code.
195+
ec = capture_errno();
196+
return false;
197+
}
198+
read_fd.fd = -1;
199+
ofstream out;
200+
out.__open(write_fd.fd, ios::binary);
201+
if (!out.is_open()) {
202+
ec = capture_errno();
203+
return false;
204+
}
205+
write_fd.fd = -1;
206+
207+
if (in.good() && out.good()) {
208+
using InIt = istreambuf_iterator<char>;
209+
using OutIt = ostreambuf_iterator<char>;
210+
InIt bin(in);
211+
InIt ein;
212+
OutIt bout(out);
213+
copy(bin, ein, bout);
214+
}
215+
if (out.fail() || in.fail()) {
216+
ec = make_error_code(errc::io_error);
217+
return false;
218+
}
219+
220+
ec.clear();
221+
return true;
222+
}
223+
#endif
224+
182225
#if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
183226
bool copy_file_impl_copy_file_range(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
184227
size_t count = read_fd.get_stat().st_size;
@@ -209,6 +252,12 @@ bool copy_file_impl_copy_file_range(FileDescriptor& read_fd, FileDescriptor& wri
209252
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
210253
bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
211254
size_t count = read_fd.get_stat().st_size;
255+
// a zero-length file is either empty, or not copyable by this syscall
256+
// return early to avoid the syscall cost
257+
if (count == 0) {
258+
ec = {EINVAL, generic_category()};
259+
return false;
260+
}
212261
do {
213262
ssize_t res;
214263
if ((res = ::sendfile(write_fd.fd, read_fd.fd, nullptr, count)) == -1) {
@@ -259,8 +308,8 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
259308
}
260309
ec.clear();
261310
# endif
262-
ec = {EINVAL, generic_category()};
263-
return false;
311+
312+
return copy_file_impl_fstream(read_fd, write_fd, ec);
264313
}
265314
#elif defined(_LIBCPP_FILESYSTEM_USE_COPYFILE)
266315
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
@@ -285,37 +334,7 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
285334
}
286335
#elif defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
287336
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
288-
ifstream in;
289-
in.__open(read_fd.fd, ios::binary);
290-
if (!in.is_open()) {
291-
// This assumes that __open didn't reset the error code.
292-
ec = capture_errno();
293-
return false;
294-
}
295-
read_fd.fd = -1;
296-
ofstream out;
297-
out.__open(write_fd.fd, ios::binary);
298-
if (!out.is_open()) {
299-
ec = capture_errno();
300-
return false;
301-
}
302-
write_fd.fd = -1;
303-
304-
if (in.good() && out.good()) {
305-
using InIt = istreambuf_iterator<char>;
306-
using OutIt = ostreambuf_iterator<char>;
307-
InIt bin(in);
308-
InIt ein;
309-
OutIt bout(out);
310-
copy(bin, ein, bout);
311-
}
312-
if (out.fail() || in.fail()) {
313-
ec = make_error_code(errc::io_error);
314-
return false;
315-
}
316-
317-
ec.clear();
318-
return true;
337+
return copy_file_impl_fstream(read_fd, write_fd, ec);
319338
}
320339
#else
321340
# error "Unknown implementation for copy_file_impl"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14
10+
// REQUIRES: linux
11+
// UNSUPPORTED: no-filesystem
12+
// UNSUPPORTED: availability-filesystem-missing
13+
14+
// <filesystem>
15+
16+
// bool copy_file(const path& from, const path& to);
17+
// bool copy_file(const path& from, const path& to, error_code& ec) noexcept;
18+
// bool copy_file(const path& from, const path& to, copy_options options);
19+
// bool copy_file(const path& from, const path& to, copy_options options,
20+
// error_code& ec) noexcept;
21+
22+
#include <filesystem>
23+
#include <cassert>
24+
25+
#include "test_macros.h"
26+
#include "filesystem_test_helper.h"
27+
28+
using namespace std::filesystem;
29+
30+
// Linux has various virtual filesystems such as /proc and /sys
31+
// where files may have no length (st_size == 0), but still contain data.
32+
// This is because the to-be-read data is usually generated ad-hoc by the reading syscall
33+
// These files can not be copied with kernel-side copies like copy_file_range or sendfile,
34+
// and must instead be copied via a traditional userspace read + write loop.
35+
int main(int, char**) {
36+
const path procfile{"/proc/self/comm"};
37+
assert(file_size(procfile) == 0);
38+
39+
scoped_test_env env;
40+
std::error_code ec = GetTestEC();
41+
42+
const path dest = env.make_env_path("dest");
43+
44+
assert(copy_file(procfile, dest, ec));
45+
assert(!ec);
46+
47+
// a tad fragile if lit ever changes the output name,
48+
// but the easiest way to assert that the correct data was copied.
49+
assert(file_size(dest) == sizeof("t.tmp.exe"));
50+
}

0 commit comments

Comments
 (0)