BSD 14 mods - move to ffmpeg6, move to python39, fix OpenEXR build; add missing inclu...
[goodguy/cinelerra.git] / cinelerra-5.1 / tools / makeappimagetool / subprocess.cpp
1 // system headers
2 #include <algorithm>
3 #include <iostream>
4 #include <memory>
5 #include <stdexcept>
6 #include <utility>
7 #include <unistd.h>
8 #include <thread>
9 #include <array>
10
11 // local headers
12 #include "includes/subprocess.h"
13 #include "includes/process.h"
14 #include "includes/pipe_reader.h"
15 #include "includes/assert.h"
16
17 namespace linuxdeploy {
18     namespace subprocess {
19         subprocess::subprocess(std::initializer_list<std::string> args, subprocess_env_map_t env)
20             : subprocess(std::vector<std::string>(args), std::move(env)) {}
21
22         subprocess::subprocess(std::vector<std::string> args, subprocess_env_map_t env)
23             : args_(std::move(args)), env_(std::move(env)) {
24             // preconditions
25             util::assert::assert_not_empty(args_);
26         }
27
28         subprocess_result subprocess::run() const {
29             process proc{args_, env_};
30
31             // create pipe readers and empty buffers for both stdout and stderr
32             // we manage them in this (admittedly, kind of complex-looking) array so we can later easily perform the
33             // operations in a loop
34             std::array<std::pair<pipe_reader, subprocess_result_buffer_t>, 2> buffers{
35                 std::make_pair(pipe_reader(proc.stdout_fd()), subprocess_result_buffer_t{}),
36                 std::make_pair(pipe_reader(proc.stderr_fd()), subprocess_result_buffer_t{}),
37             };
38
39             for (;;) {
40                 for (auto& pair : buffers) {
41                     // make code more readable
42                     auto& reader = pair.first;
43                     auto& buffer = pair.second;
44
45                     // read some bytes into smaller intermediate buffer to prevent either of the pipes to overflow
46                     // the results are immediately appended to the main buffer
47                     subprocess_result_buffer_t intermediate_buffer(4096);
48
49                     // (try to) read all available data from pipe
50                     for (;;) {
51                         const auto bytes_read = reader.read(intermediate_buffer);
52
53                         if (bytes_read == 0) {
54                             break;
55                         }
56
57                         // append to main buffer
58                         buffer.reserve(buffer.size() + bytes_read);
59                         std::copy(intermediate_buffer.begin(), (intermediate_buffer.begin() + bytes_read),
60                                   std::back_inserter(buffer));
61                     }
62                 }
63
64                 // do-while might be a little more elegant, but we can save this one unnecessary sleep, so...
65                 if (proc.is_running()) {
66                     // reduce load on CPU
67                     std::this_thread::sleep_for(std::chrono::milliseconds(50));
68                 } else {
69                     break;
70                 }
71             }
72
73             // make sure contents are null-terminated
74             buffers[0].second.emplace_back('\0');
75             buffers[1].second.emplace_back('\0');
76
77             auto exit_code = proc.close();
78
79             return subprocess_result{exit_code, buffers[0].second, buffers[1].second};
80         }
81
82         std::string subprocess::check_output() const {
83             const auto result = run();
84
85             if (result.exit_code() != 0) {
86                 throw std::logic_error{"subprocess failed (exit code " + std::to_string(result.exit_code()) + ")"};
87             }
88
89             return result.stdout_string();
90         }
91     }
92 }