Ilya_konstantinov

Untitled

Mar 20th, 2026 (edited)
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 13.67 KB | None | 0 0
  1. ######################## capture.cpp
  2.  
  3. #pragma once
  4.  
  5. #include <string>
  6. #include <string_view>
  7. #include <utility>
  8. #include <variant>
  9.  
  10. #include <unused.hpp>  // TODO: remove before flight.
  11.  
  12. template <class F>
  13. std::variant<std::pair<std::string, std::string>, int>
  14. CaptureOutput(F&& f, std::string_view input) {
  15.     UNUSED(f, input);  // TODO: remove before flight.
  16.     // TODO: your code here.
  17.     throw "TODO";
  18. }
  19.  
  20.  
  21. ######################## distribution.hpp
  22.  
  23. #pragma once
  24.  
  25. #include <random>
  26.  
  27. struct UniformCharDistribution {
  28.     using result_type = char;
  29.  
  30.     UniformCharDistribution(char lhs, char rhs)
  31.         : impl_(static_cast<int>(lhs), static_cast<int>(rhs)) {
  32.     }
  33.  
  34.     template <class Rng>
  35.     char operator()(Rng& rng) {
  36.         return static_cast<char>(impl_(rng));
  37.     }
  38.  
  39.   private:
  40.     std::uniform_int_distribution<int> impl_;
  41. };
  42.  
  43.  
  44. ######################## fd-guard.cpp
  45.  
  46. #include "fd-guard.hpp"
  47. #include <cassert>
  48. #include <algorithm>
  49. #include <filesystem>
  50. #include <unistd.h>
  51.  
  52. #include <fcntl.h>
  53. #include <linux/kcmp.h>
  54. #include <sys/resource.h>
  55. #include <sys/syscall.h>
  56.  
  57. #define WARN(v)
  58.  
  59. namespace fs = std::filesystem;
  60.  
  61. static std::vector<int> GetOpenFileDescriptors() {
  62.     std::vector<int> fds_candidates;
  63.     for (auto dent : fs::directory_iterator{"/proc/self/fd"}) {
  64.         fds_candidates.push_back(std::stoi(dent.path().filename()));
  65.     }
  66.     std::vector<int> fds;
  67.     for (auto fd : fds_candidates) {
  68.         int ret = fcntl(fd, F_GETFD);
  69.         if (ret == -1) {
  70.             continue;
  71.         }
  72.         fds.push_back(fd);
  73.     }
  74.     std::sort(fds.begin(), fds.end());
  75.  
  76.     return fds;
  77. }
  78.  
  79. static size_t MaxFdNum() {
  80.     struct rlimit limits{};
  81.  
  82.     int r = ::getrlimit(RLIMIT_NOFILE, &limits);
  83.     assert(r != -1);
  84.     // Probably should check `/proc/sys/fs/nr_open` instead
  85.     return std::min(static_cast<size_t>(limits.rlim_cur), size_t{2048});
  86. }
  87.  
  88. FileDescriptorsGuard::FileDescriptorsGuard() {
  89.     auto fds = GetOpenFileDescriptors();
  90.     if (fds.empty()) {
  91.         return;
  92.     }
  93.  
  94.     auto max_fd_num = MaxFdNum();
  95.     assert(2 * fds.size() < max_fd_num);
  96.  
  97.     auto cur_fd_num = static_cast<int>(max_fd_num - 1);
  98.     for (auto fd : fds) {
  99.         while (std::ranges::find(fds, cur_fd_num) != fds.end()) {
  100.             --cur_fd_num;
  101.         }
  102.  
  103.         int ret = dup3(fd, cur_fd_num, O_CLOEXEC);
  104.         assert(ret != -1);
  105.         copies.emplace_back(fd, cur_fd_num);
  106.         --cur_fd_num;
  107.     }
  108. }
  109.  
  110. bool FileDescriptorsGuard::TestDescriptorsState() const {
  111.     for (auto [from, to] : copies) {
  112.         if (!TestSameFd(from, to)) {
  113.             WARN("Descriptors " << from << " and " << to
  114.                                 << " don't refer to the same file");
  115.             return false;
  116.         }
  117.     }
  118.  
  119.     auto fds = GetOpenFileDescriptors();
  120.     auto expected = OpenFdsCount();
  121.     if (fds.size() != expected) {
  122.         WARN("Descriptors count mismatch. Expected "
  123.              << expected << ", actually " << fds.size());
  124.         return false;
  125.     }
  126.     return true;
  127. }
  128.  
  129. size_t FileDescriptorsGuard::OpenFdsCount() const {
  130.     return copies.size() * 2;
  131. }
  132.  
  133. bool FileDescriptorsGuard::TestSameFd(int fd1, int fd2) const {
  134.     int pid = getpid();
  135.     int ret = syscall(SYS_kcmp, pid, pid, KCMP_FILE, fd1, fd2);
  136.     if (ret == -1) {
  137.         int err = errno;
  138.         if (err == ENOSYS) {
  139.             if (ShouldWarnKCmp()) {
  140.                 WARN("KCmp is not available on your system, some checks "
  141.                      "wouldn't be performed");
  142.             }
  143.             return true;
  144.         }
  145.         assert(err == EBADF);
  146.         return false;
  147.     }
  148.     return ret == 0;
  149. }
  150.  
  151. bool FileDescriptorsGuard::ShouldWarnKCmp() const {
  152.     return !std::exchange(warned_kcmp_, true);
  153. }
  154.  
  155. FileDescriptorsGuard::~FileDescriptorsGuard() {
  156.     for (auto [_, fd] : copies) {
  157.         close(fd);
  158.     }
  159. }
  160.  
  161.  
  162. ######################## fd-guard.hpp
  163.  
  164. #pragma once
  165.  
  166. #ifndef __linux__
  167. #error "Only linux is supported for this problem"
  168. #endif
  169.  
  170. #include <cstddef>
  171. #include <utility>
  172. #include <vector>
  173.  
  174. struct FileDescriptorsGuard {
  175.     FileDescriptorsGuard();
  176.  
  177.     [[nodiscard]] bool TestDescriptorsState() const;
  178.  
  179.     size_t OpenFdsCount() const;
  180.  
  181.     ~FileDescriptorsGuard();
  182.  
  183.   private:
  184.     bool ShouldWarnKCmp() const;
  185.     bool TestSameFd(int fd1, int fd2) const;
  186.  
  187.     std::vector<std::pair<int, int>> copies;
  188.     mutable bool warned_kcmp_{false};
  189. };
  190.  
  191. ######################## overload.hpp
  192.  
  193. #pragma once
  194.  
  195. template <class... Ts>
  196. struct Overload : Ts... {
  197.     using Ts::operator()...;
  198. };
  199.  
  200. template <class... Ts>
  201. Overload(Ts...) -> Overload<Ts...>;
  202.  
  203.  
  204. ######################## rim-guard.cpp
  205.  
  206. #include <cassert>
  207. #include "rlim-guard.hpp"
  208.  
  209. #include <sys/resource.h>
  210. #include <utility>
  211.  
  212. static uint64_t ModifySoftLimit(int limit_type, uint64_t new_limit) {
  213.     struct rlimit limit{};
  214.     {
  215.         int ret = getrlimit(limit_type, &limit);
  216.         assert(ret != -1);
  217.     }
  218.  
  219.     auto new_limit_inner = static_cast<rlim_t>(new_limit);
  220.     assert(new_limit_inner <= limit.rlim_max);
  221.     auto prev_limit = std::exchange(limit.rlim_cur, new_limit_inner);
  222.  
  223.     {
  224.         int ret = setrlimit(limit_type, &limit);
  225.         assert(ret != -1);
  226.     }
  227.     return static_cast<uint64_t>(prev_limit);
  228. }
  229.  
  230. RLimGuard::RLimGuard(int limit_type, rlim_t new_limit)
  231.     : limit_type_(limit_type) {
  232.     prev_limit_ = ModifySoftLimit(limit_type_, new_limit);
  233. }
  234.  
  235. void RLimGuard::Reset() {
  236.     auto limit_type = std::exchange(limit_type_, -1);
  237.     if (limit_type == -1) {
  238.         return;
  239.     }
  240.     ModifySoftLimit(limit_type, prev_limit_);
  241. }
  242.  
  243. RLimGuard::~RLimGuard() {
  244.     Reset();
  245. }
  246.  
  247. ######################## rim-guard.hpp
  248.  
  249.  
  250. #pragma once
  251.  
  252. #include <cstdint>
  253.  
  254. struct RLimGuard {
  255.     RLimGuard(int limit_type, uint64_t new_limit);
  256.  
  257.     RLimGuard(const RLimGuard&) = delete;
  258.     RLimGuard(RLimGuard&&) = delete;
  259.  
  260.     RLimGuard& operator=(const RLimGuard&) = delete;
  261.     RLimGuard& operator=(RLimGuard&&) = delete;
  262.  
  263.     void Reset();
  264.  
  265.     ~RLimGuard();
  266.  
  267.   private:
  268.     int limit_type_;
  269.     uint64_t prev_limit_;
  270. };
  271.  
  272. ######################## test.cpp
  273.  
  274. #include "capture.hpp"
  275.  
  276. #include <cassert>
  277. #include "distributions.hpp"
  278. #include "fd-guard.hpp"
  279. #include "overload.hpp"
  280. #include "rlim-guard.hpp"
  281.  
  282. #include <algorithm>
  283. #include <iostream>
  284. #include <random>
  285.  
  286. #include <fcntl.h>
  287. #include <sys/resource.h>
  288. #include <unistd.h>
  289.  
  290. #define TEST_CASE(v)
  291. #define SECTION(v)
  292. #define INFO(v)
  293. #define REQUIRE(v) assert(v)
  294. #define CHECK(v) assert(v)
  295.  
  296.  
  297. void Flush() {
  298.     std::cout.flush();
  299.     std::clog.flush();
  300.     std::fflush(stdout);
  301.     std::fflush(stderr);
  302. }
  303.  
  304. template <class Rng>
  305. std::string GenerateStr(Rng& rng, size_t n) {
  306.     UniformCharDistribution dist('a', 'z');
  307.     std::string s(n, ' ');
  308.     std::generate(s.begin(), s.end(), [&rng, &dist] { return dist(rng); });
  309.     return s;
  310. }
  311.  
  312. void ResetCinState() {
  313.     std::cin.clear();
  314.     std::clearerr(stdin);
  315. }
  316.  
  317. static constexpr size_t kPipeSize = 4 << 10;
  318.  
  319. int main() {
  320. TEST_CASE("CheckPipeCapacity") {
  321.     INFO("This is an internal assertion, please report if it fails");
  322.     int fds[2];
  323.     int r = pipe(fds);
  324.     REQUIRE(r != -1);
  325.     int cap = fcntl(fds[0], F_GETPIPE_SZ);
  326.     {
  327.         int err = errno;
  328.         INFO("Errno is " << err);
  329.         REQUIRE(cap != -1);
  330.     }
  331.     REQUIRE(static_cast<size_t>(cap) >= kPipeSize);
  332.     close(fds[0]);
  333.     close(fds[1]);
  334. }
  335.  
  336. TEST_CASE("JustWorks") {
  337.     FileDescriptorsGuard guard;
  338.  
  339.     SECTION("Simple") {
  340.         int runs = 0;
  341.         Flush();
  342.         auto result = CaptureOutput([&runs] { ++runs; }, "");
  343.         REQUIRE(result.index() == 0);
  344.         auto [out, err] = std::get<0>(std::move(result));
  345.         CHECK(out == "");
  346.         CHECK(err == "");
  347.         CHECK(runs == 1);
  348.         CHECK(guard.TestDescriptorsState());
  349.     }
  350.  
  351.     SECTION("Output") {
  352.         Flush();
  353.         auto result = CaptureOutput(
  354.             [] {
  355.                 std::cout << "Aba";
  356.                 std::cout.flush();
  357.                 std::cerr << "Caba";
  358.             },
  359.             "");
  360.         REQUIRE(result.index() == 0);
  361.         auto [out, err] = std::get<0>(std::move(result));
  362.         CHECK(out == "Aba");
  363.         CHECK(err == "Caba");
  364.         CHECK(guard.TestDescriptorsState());
  365.     }
  366.  
  367.     SECTION("Input+Output") {
  368.         Flush();
  369.         auto result = CaptureOutput(
  370.             [] {
  371.                 for (size_t i = 0; i < 3; ++i) {
  372.                     int v;
  373.                     std::cin >> v;
  374.                     if (v & 1) {
  375.                         std::cout << v << " ";
  376.                     } else {
  377.                         std::cerr << v << " ";
  378.                     }
  379.                 }
  380.  
  381.                 std::cout.flush();
  382.             },
  383.             "1 2 3 ");
  384.  
  385.         REQUIRE(result.index() == 0);
  386.         auto [out, err] = std::get<0>(std::move(result));
  387.         CHECK(out == "1 3 ");
  388.         CHECK(err == "2 ");
  389.         CHECK(guard.TestDescriptorsState());
  390.     }
  391. }
  392.  
  393. TEST_CASE("EOF") {
  394.     FileDescriptorsGuard guard;
  395.  
  396.     Flush();
  397.     auto result = CaptureOutput(
  398.         [] {
  399.             int v;
  400.             while (std::cin >> v) {
  401.                 if (v & 1) {
  402.                     std::cout << v;
  403.                 } else {
  404.                     std::cerr << v;
  405.                 }
  406.             }
  407.             std::cout.flush();
  408.         },
  409.         "1 2  3 4 5  6 7 8");
  410.     ResetCinState();
  411.  
  412.     REQUIRE(result.index() == 0);
  413.     auto [out, err] = std::get<0>(std::move(result));
  414.     CHECK(out == "1357");
  415.     CHECK(err == "2468");
  416.     CHECK(guard.TestDescriptorsState());
  417. }
  418.  
  419. TEST_CASE("HugeIO") {
  420.     FileDescriptorsGuard guard;
  421.  
  422.     static constexpr size_t kBufSize = kPipeSize;
  423.     std::mt19937 rng(42);
  424.  
  425.     auto inp = GenerateStr(rng, kBufSize);
  426.     auto my_out = GenerateStr(rng, kBufSize);
  427.     auto my_err = GenerateStr(rng, kBufSize);
  428.  
  429.     std::string actual_inp;
  430.     Flush();
  431.     auto result = CaptureOutput(
  432.         [&] {
  433.             std::string s;
  434.             std::cin >> s;
  435.  
  436.             std::cout << my_out;
  437.             std::cerr << my_err;
  438.  
  439.             actual_inp = std::move(s);
  440.             std::cout.flush();
  441.         },
  442.         inp);
  443.     ResetCinState();
  444.  
  445.     CHECK(actual_inp == inp);
  446.  
  447.     REQUIRE(result.index() == 0);
  448.     auto [out, err] = std::get<0>(std::move(result));
  449.     CHECK(out == my_out);
  450.     CHECK(err == my_err);
  451.     CHECK(guard.TestDescriptorsState());
  452. }
  453.  
  454. TEST_CASE("ErrorRecovery") {
  455.     std::mt19937 rng(42);
  456.  
  457.     FileDescriptorsGuard guard;
  458.     constexpr auto base_fd_count = 3;
  459.  
  460.     for (size_t i = 0; i <= 10; ++i) {
  461.         INFO("i = " << i);
  462.  
  463.         auto out = GenerateStr(rng, 10);
  464.         auto err = GenerateStr(rng, 10);
  465.         auto inp = GenerateStr(rng, 10);
  466.  
  467.         Flush();
  468.         auto result = [&] {
  469.             RLimGuard files_guard(RLIMIT_NOFILE, base_fd_count + i);
  470.             return CaptureOutput(
  471.                 [&] {
  472.                     (std::cout << out).flush();
  473.                     std::cerr << err;
  474.                 },
  475.                 inp);
  476.         }();
  477.  
  478.         if (i < 2) {
  479.             REQUIRE(result.index() == 1);
  480.         }
  481.         if (i > 9) {
  482.             REQUIRE(result.index() == 0);
  483.         }
  484.  
  485.         std::visit(Overload{
  486.                        [&](std::pair<std::string, std::string> output) {
  487.                            CHECK(output.first == out);
  488.                            CHECK(output.second == err);
  489.                        },
  490.                        [](int err) {
  491.                            INFO("Error code: " << err << " "
  492.                                                << std::strerror(err));
  493.                            REQUIRE((err == EBADF || err == EMFILE));
  494.                        },
  495.                    },
  496.                    std::move(result));
  497.  
  498.         CHECK(guard.TestDescriptorsState());
  499.     }
  500. }
  501.  
  502. TEST_CASE("RawIO") {
  503.     constexpr size_t kBufSize = 10;
  504.  
  505.     std::mt19937 rng(42);
  506.     auto input = GenerateStr(rng, kBufSize);
  507.     auto output = GenerateStr(rng, kBufSize);
  508.     auto error = GenerateStr(rng, kBufSize);
  509.  
  510.     auto write_all = [](int fd, std::string_view data) -> int {
  511.         while (!data.empty()) {
  512.             int w = write(fd, data.data(), data.size());
  513.             if (w == -1) {
  514.                 return -errno;
  515.             }
  516.             data = data.substr(w);
  517.         }
  518.         return 0;
  519.     };
  520.  
  521.     char real_input[kBufSize + 1];
  522.  
  523.     int out_err = 0;
  524.     int err_err = 0;
  525.     int in_err = 0;
  526.     auto result = CaptureOutput(
  527.         [&] {
  528.             out_err = write_all(STDOUT_FILENO, output);
  529.             err_err = write_all(STDERR_FILENO, error);
  530.  
  531.             char* in = real_input;
  532.             char* real_in_end = real_input + kBufSize;
  533.             while (in < real_in_end) {
  534.                 int r = read(0, in, real_in_end - in);
  535.                 if (r == -1) {
  536.                     in_err = -errno;
  537.                     break;
  538.                 }
  539.                 if (r == 0) {
  540.                     in_err = 1023;
  541.                     break;
  542.                 }
  543.                 in += r;
  544.             }
  545.  
  546.             if (read(0, in, 1) != 0) {
  547.                 in_err = 1024;
  548.             }
  549.         },
  550.         input);
  551.  
  552.     REQUIRE(out_err == 0);
  553.     REQUIRE(err_err == 0);
  554.     REQUIRE(in_err == 0);
  555.  
  556.     REQUIRE(result.index() == 0);
  557.     auto [out, err] = std::get<0>(result);
  558.  
  559.     CHECK(out == output);
  560.     CHECK(err == error);
  561.     auto val = std::string_view{real_input, kBufSize};
  562.     assert(val == input);
  563. }
  564. }
  565.  
  566. ######################## make.sh
  567.  
  568. #!/bin/bash
  569. SCR=solution
  570. OUT=runnable
  571.  
  572. cp $filename capture.hpp
  573.  
  574. g++ fd-guard.cpp rim-guard.cpp test.cpp -fsanitize=address -std=c++20 -o $OUT
Advertisement
Add Comment
Please, Sign In to add comment