Add file copy example.
diff --git a/asio/src/examples/cpp20/files/recursive_file_copy.cpp b/asio/src/examples/cpp20/files/recursive_file_copy.cpp
new file mode 100644
index 0000000..f829f9d
--- /dev/null
+++ b/asio/src/examples/cpp20/files/recursive_file_copy.cpp
@@ -0,0 +1,176 @@
+#include <asio.hpp>
+#include <asio/awaitable.hpp>
+#include <asio/experimental/as_tuple.hpp>
+#include <asio/experimental/awaitable_operators.hpp>
+#include <cstdio>
+#include <filesystem>
+
+using namespace asio;
+using namespace asio::experimental;
+using namespace asio::experimental::awaitable_operators;
+namespace fs = std::filesystem;
+using stream_file = asio::posix::stream_descriptor;
+
+constexpr std::size_t buf_size = 65536;
+constexpr std::size_t buf_align = 512;
+constexpr std::size_t active_copies_high_watermark = 500;
+constexpr std::size_t active_copies_low_watermark = 400;
+
+stream_file open_file(const any_io_executor& ex, const fs::path& p, int flags, int mode = 0)
+{
+ int fd = ::open(p.c_str(), flags, mode);
+ if (fd < 0)
+ {
+ int err = errno;
+ throw std::system_error(std::error_code(err, std::system_category()));
+ }
+ return stream_file(ex, fd);
+}
+
+stream_file open_file_read_only(const any_io_executor& ex, const fs::path& p)
+{
+ return open_file(ex, p, O_RDONLY);
+}
+
+stream_file open_file_write_only(const any_io_executor& ex, const fs::path& p)
+{
+ return open_file(ex, p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+}
+
+mutable_buffer align(mutable_buffer buf)
+{
+ void* data = buf.data();
+ std::size_t size = buf.size();
+ if (std::align(buf_align, buf_size, data, size) == nullptr)
+ std::abort();
+ return mutable_buffer(data, size);
+}
+
+awaitable<std::size_t> async_copy_file(const fs::path& from, const fs::path& to)
+{
+ stream_file from_file = open_file_read_only(co_await this_coro::executor, from);
+ stream_file to_file = open_file_write_only(co_await this_coro::executor, to);
+
+ std::vector<std::byte> buf_space(buf_size + buf_align);
+ auto buf = align(buffer(buf_space));
+
+ std::size_t bytes_copied = 0;
+ while (true)
+ {
+ auto [e, n] = co_await from_file.async_read_some(buf, as_tuple(use_awaitable));
+ if (e == stream_errc::eof) break;
+ if (e) throw std::system_error(e);
+ co_await async_write(to_file, buffer(buf, n), use_awaitable);
+ bytes_copied += n;
+ }
+ co_return bytes_copied;
+}
+
+awaitable<std::size_t> copy_one_file(const fs::path& from, const fs::path& to)
+{
+ try
+ {
+ auto bytes_copied = co_await async_copy_file(from, to);
+ std::printf("copied %ld bytes from %s to %s\n", bytes_copied, from.c_str(), to.c_str());
+ co_return bytes_copied;
+ }
+ catch (const std::exception& e)
+ {
+ std::fprintf(stderr, "exception copying from %s to %s: \n", from.c_str(), to.c_str());
+ co_return 0;
+ }
+}
+
+awaitable<void> wait_for_turn(steady_timer& turn_timer, std::size_t& active_copies)
+{
+ while (active_copies >= active_copies_high_watermark)
+ co_await turn_timer.async_wait(as_tuple(use_awaitable));
+ ++active_copies;
+}
+
+void end_turn(steady_timer& turn_timer, std::size_t& active_copies)
+{
+ if (--active_copies <= active_copies_low_watermark)
+ turn_timer.cancel();
+}
+
+awaitable<std::size_t> queue_file_copy(const fs::path& from, fs::directory_entry& entry,
+ const fs::path& to, steady_timer& turn_timer, std::size_t& active_copies)
+{
+ if (!entry.is_directory())
+ {
+ auto relative_source = fs::relative(entry.path(), from);
+ auto target_parent_path = to / relative_source.parent_path();
+ auto target_parent_file = target_parent_path / entry.path().filename();
+ fs::create_directories(target_parent_path);
+ co_await wait_for_turn(turn_timer, active_copies);
+ auto bytes_copied = co_await copy_one_file(entry.path(), target_parent_file);
+ end_turn(turn_timer, active_copies);
+ co_return bytes_copied;
+ }
+ co_return 0;
+}
+
+awaitable<std::size_t> copy_files(const fs::path& from,
+ std::vector<fs::directory_entry>::iterator first,
+ std::vector<fs::directory_entry>::iterator last, const fs::path& to,
+ steady_timer& turn_timer, std::size_t& active_copies)
+{
+ auto n = last - first;
+ if (n == 1)
+ co_return co_await queue_file_copy(from, *first, to, turn_timer, active_copies);
+ else if (n > 1)
+ {
+ auto [n1, n2] = co_await (
+ copy_files(from, first, first + n / 2, to, turn_timer, active_copies)
+ && copy_files(from, first + n / 2, last, to, turn_timer, active_copies)
+ );
+ co_return n1 + n2;
+ }
+ else
+ co_return 0;
+}
+
+awaitable<std::size_t> copy_files(const fs::path& from, const fs::path& to)
+{
+ steady_timer turn_timer(co_await this_coro::executor);
+ turn_timer.expires_at(steady_timer::time_point::max());
+ std::size_t active_copies = 0;
+
+ std::vector<fs::directory_entry> entries{
+ fs::recursive_directory_iterator(from),
+ fs::recursive_directory_iterator()};
+
+ co_return co_await copy_files(from, entries.begin(),
+ entries.end(), to, turn_timer, active_copies);
+}
+
+int main(int argc, const char* argv[])
+{
+ try
+ {
+ if (argc != 3)
+ {
+ std::fprintf(stderr, "Usage: file_copy <from-dir> <to-dir>\n");
+ return 1;
+ }
+
+ fs::path from = argv[1];
+ fs::path to = argv[2];
+
+ asio::io_context ctx(1);
+
+ co_spawn(ctx,
+ copy_files(from, to),
+ [](std::exception_ptr, std::size_t n)
+ {
+ std::printf("%ld bytes copied\n", n);
+ });
+
+ ctx.run();
+ }
+ catch (const std::exception& e)
+ {
+ std::fprintf(stderr, "Exception: %s\n", e.what());
+ }
+}