Skip to content

Commit 6009df0

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 2e871a9 commit 6009df0

File tree

2 files changed

+105
-34
lines changed

2 files changed

+105
-34
lines changed

libcxx/src/filesystem/operations.cpp

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,17 @@
4949
# include <copyfile.h>
5050
# define _LIBCPP_FILESYSTEM_USE_COPYFILE
5151
#else
52-
# include <fstream>
5352
# define _LIBCPP_FILESYSTEM_USE_FSTREAM
5453
#endif
5554

55+
// sendfile and copy_file_range need to fall back
56+
// to the fstream implementation for special files
57+
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE) || defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE) || \
58+
defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
59+
# include <fstream>
60+
# define _LIBCPP_FILESYSTEM_NEED_FSTREAM
61+
#endif
62+
5663
#if defined(__ELF__) && defined(_LIBCPP_LINK_RT_LIB)
5764
# pragma comment(lib, "rt")
5865
#endif
@@ -183,6 +190,42 @@ void __copy(const path& from, const path& to, copy_options options, error_code*
183190
namespace detail {
184191
namespace {
185192

193+
#if defined(_LIBCPP_FILESYSTEM_NEED_FSTREAM)
194+
bool copy_file_impl_fstream(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
195+
ifstream in;
196+
in.__open(read_fd.fd, ios::binary);
197+
if (!in.is_open()) {
198+
// This assumes that __open didn't reset the error code.
199+
ec = capture_errno();
200+
return false;
201+
}
202+
read_fd.fd = -1;
203+
ofstream out;
204+
out.__open(write_fd.fd, ios::binary);
205+
if (!out.is_open()) {
206+
ec = capture_errno();
207+
return false;
208+
}
209+
write_fd.fd = -1;
210+
211+
if (in.good() && out.good()) {
212+
using InIt = istreambuf_iterator<char>;
213+
using OutIt = ostreambuf_iterator<char>;
214+
InIt bin(in);
215+
InIt ein;
216+
OutIt bout(out);
217+
copy(bin, ein, bout);
218+
}
219+
if (out.fail() || in.fail()) {
220+
ec = make_error_code(errc::io_error);
221+
return false;
222+
}
223+
224+
ec.clear();
225+
return true;
226+
}
227+
#endif
228+
186229
#if defined(_LIBCPP_FILESYSTEM_USE_COPY_FILE_RANGE)
187230
bool copy_file_impl_copy_file_range(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
188231
size_t count = read_fd.get_stat().st_size;
@@ -214,6 +257,12 @@ bool copy_file_impl_copy_file_range(FileDescriptor& read_fd, FileDescriptor& wri
214257
#if defined(_LIBCPP_FILESYSTEM_USE_SENDFILE)
215258
bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
216259
size_t count = read_fd.get_stat().st_size;
260+
// a zero-length file is either empty, or not copyable by this syscall
261+
// return early to avoid the syscall cost
262+
if (count == 0) {
263+
ec = {EINVAL, generic_category()};
264+
return false;
265+
}
217266
do {
218267
ssize_t res;
219268
if ((res = ::sendfile(write_fd.fd, read_fd.fd, nullptr, count)) == -1) {
@@ -264,8 +313,8 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
264313
}
265314
ec.clear();
266315
# endif
267-
ec = {EINVAL, generic_category()};
268-
return false;
316+
317+
return copy_file_impl_fstream(read_fd, write_fd, ec);
269318
}
270319
#elif defined(_LIBCPP_FILESYSTEM_USE_COPYFILE)
271320
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
@@ -290,37 +339,7 @@ bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_cod
290339
}
291340
#elif defined(_LIBCPP_FILESYSTEM_USE_FSTREAM)
292341
bool copy_file_impl(FileDescriptor& read_fd, FileDescriptor& write_fd, error_code& ec) {
293-
ifstream in;
294-
in.__open(read_fd.fd, ios::binary);
295-
if (!in.is_open()) {
296-
// This assumes that __open didn't reset the error code.
297-
ec = capture_errno();
298-
return false;
299-
}
300-
read_fd.fd = -1;
301-
ofstream out;
302-
out.__open(write_fd.fd, ios::binary);
303-
if (!out.is_open()) {
304-
ec = capture_errno();
305-
return false;
306-
}
307-
write_fd.fd = -1;
308-
309-
if (in.good() && out.good()) {
310-
using InIt = istreambuf_iterator<char>;
311-
using OutIt = ostreambuf_iterator<char>;
312-
InIt bin(in);
313-
InIt ein;
314-
OutIt bout(out);
315-
copy(bin, ein, bout);
316-
}
317-
if (out.fail() || in.fail()) {
318-
ec = make_error_code(errc::io_error);
319-
return false;
320-
}
321-
322-
ec.clear();
323-
return true;
342+
return copy_file_impl_fstream(read_fd, write_fd, ec);
324343
}
325344
#else
326345
# error "Unknown implementation for copy_file_impl"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 <cassert>
23+
#include <filesystem>
24+
#include <system_error>
25+
26+
#include "test_macros.h"
27+
#include "filesystem_test_helper.h"
28+
29+
namespace fs = std::filesystem;
30+
31+
// Linux has various virtual filesystems such as /proc and /sys
32+
// where files may have no length (st_size == 0), but still contain data.
33+
// This is because the to-be-read data is usually generated ad-hoc by the reading syscall
34+
// These files can not be copied with kernel-side copies like copy_file_range or sendfile,
35+
// and must instead be copied via a traditional userspace read + write loop.
36+
int main(int, char** argv) {
37+
const fs::path procfile{"/proc/self/comm"};
38+
assert(file_size(procfile) == 0);
39+
40+
scoped_test_env env;
41+
std::error_code ec = GetTestEC();
42+
43+
const fs::path dest = env.make_env_path("dest");
44+
45+
assert(copy_file(procfile, dest, ec));
46+
assert(!ec);
47+
48+
// /proc/self/comm contains the filename of the executable, plus a null terminator
49+
assert(file_size(dest) == fs::path(argv[0]).filename().string().size() + 1);
50+
51+
return 0;
52+
}

0 commit comments

Comments
 (0)