55
66#include " logging/logging.h"
77
8+ #include < array>
9+ #include < iostream>
10+ #include < string>
11+ #include < vector>
12+ #include < unistd.h>
13+ #include < sys/wait.h>
14+ #include < spawn.h>
15+ #include < cstring>
16+ #include < unistd.h>
17+ #include < fcntl.h>
18+
19+ struct ProcessResult {
20+ std::string stdout_output;
21+ std::string stderr_output;
22+ int exit_code;
23+ };
24+
25+ class Process {
26+ private:
27+ int stdout_pipe[2 ];
28+ int stderr_pipe[2 ];
29+ pid_t pid;
30+
31+ // Set file descriptor to non-blocking mode
32+ bool setNonBlocking (int fd) {
33+ int flags = fcntl (fd, F_GETFL, 0 );
34+ if (flags == -1 ) return false ;
35+ return fcntl (fd, F_SETFL, flags | O_NONBLOCK) != -1 ;
36+ }
37+
38+ // Read from both pipes simultaneously to avoid deadlock
39+ void readFromBothPipes (std::string& stdout_data, std::string& stderr_data) {
40+ std::array<char , 4096 > buffer;
41+ fd_set read_fds;
42+ int max_fd = std::max (stdout_pipe[0 ], stderr_pipe[0 ]) + 1 ;
43+ bool stdout_open = true ;
44+ bool stderr_open = true ;
45+
46+ // Set both pipes to non-blocking mode
47+ setNonBlocking (stdout_pipe[0 ]);
48+ setNonBlocking (stderr_pipe[0 ]);
49+
50+ while (stdout_open || stderr_open) {
51+ FD_ZERO (&read_fds);
52+
53+ if (stdout_open) FD_SET (stdout_pipe[0 ], &read_fds);
54+ if (stderr_open) FD_SET (stderr_pipe[0 ], &read_fds);
55+
56+ int result = select (max_fd, &read_fds, nullptr , nullptr , NULL );
57+ if (result == -1 ) {
58+ LOG_ERROR << " Error in select: " << strerror (errno);
59+ throw std::runtime_error (" exec: Error creating pipes" );
60+ }
61+
62+ // Read from stdout if data is available
63+ if (stdout_open && FD_ISSET (stdout_pipe[0 ], &read_fds)) {
64+ ssize_t bytes_read = read (stdout_pipe[0 ], buffer.data (), buffer.size ());
65+ if (bytes_read > 0 ) {
66+ stdout_data.append (buffer.data (), bytes_read);
67+ } else if (bytes_read == 0 ) {
68+ stdout_open = false ;
69+ } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
70+ stdout_open = false ;
71+ }
72+ }
73+
74+ // Read from stderr if data is available
75+ if (stderr_open && FD_ISSET (stderr_pipe[0 ], &read_fds)) {
76+ ssize_t bytes_read = read (stderr_pipe[0 ], buffer.data (), buffer.size ());
77+ if (bytes_read > 0 ) {
78+ stderr_data.append (buffer.data (), bytes_read);
79+ } else if (bytes_read == 0 ) {
80+ stderr_open = false ;
81+ } else if (errno != EAGAIN && errno != EWOULDBLOCK) {
82+ stderr_open = false ;
83+ }
84+ }
85+ }
86+ }
87+
88+ public:
89+ Process () : pid(-1 ) {
90+ stdout_pipe[0 ] = stdout_pipe[1 ] = -1 ;
91+ stderr_pipe[0 ] = stderr_pipe[1 ] = -1 ;
92+ }
93+
94+ ~Process () {
95+ closeAllPipes ();
96+ }
97+
98+ void closeAllPipes () {
99+ if (stdout_pipe[0 ] != -1 ) close (stdout_pipe[0 ]);
100+ if (stdout_pipe[1 ] != -1 ) close (stdout_pipe[1 ]);
101+ if (stderr_pipe[0 ] != -1 ) close (stderr_pipe[0 ]);
102+ if (stderr_pipe[1 ] != -1 ) close (stderr_pipe[1 ]);
103+
104+ stdout_pipe[0 ] = stdout_pipe[1 ] = -1 ;
105+ stderr_pipe[0 ] = stderr_pipe[1 ] = -1 ;
106+ }
107+
108+ ProcessResult execute (const std::string& command) {
109+ ProcessResult result;
110+ result.exit_code = -1 ;
111+
112+ // Create pipes for stdout and stderr
113+ if (pipe (stdout_pipe) == -1 || pipe (stderr_pipe) == -1 ) {
114+ LOG_ERROR << " Error creating pipes: " << strerror (errno);
115+ throw std::runtime_error (" exec: Error creating pipes" );
116+ }
117+
118+ // Setup file actions for posix_spawn
119+ posix_spawn_file_actions_t file_actions;
120+ posix_spawn_file_actions_init (&file_actions);
121+
122+ // Redirect stdout to pipe
123+ posix_spawn_file_actions_addclose (&file_actions, stdout_pipe[0 ]);
124+ posix_spawn_file_actions_adddup2 (&file_actions, stdout_pipe[1 ], STDOUT_FILENO);
125+ posix_spawn_file_actions_addclose (&file_actions, stdout_pipe[1 ]);
126+
127+ // Redirect stderr to pipe
128+ posix_spawn_file_actions_addclose (&file_actions, stderr_pipe[0 ]);
129+ posix_spawn_file_actions_adddup2 (&file_actions, stderr_pipe[1 ], STDERR_FILENO);
130+ posix_spawn_file_actions_addclose (&file_actions, stderr_pipe[1 ]);
131+
132+ // Prepare arguments for shell execution
133+ char *argv[] = {
134+ const_cast <char *>(" sh" ),
135+ const_cast <char *>(" -c" ),
136+ const_cast <char *>(command.c_str ()),
137+ nullptr
138+ };
139+
140+ // Spawn the process
141+ int spawn_result = posix_spawn (&pid, " /bin/sh" , &file_actions, nullptr , argv, environ);
142+
143+ // Clean up file actions
144+ posix_spawn_file_actions_destroy (&file_actions);
145+
146+ if (spawn_result != 0 ) {
147+ LOG_ERROR << " Error spawning process: " << strerror (spawn_result);
148+ closeAllPipes ();
149+ throw std::runtime_error (" exec: Error spawning process" );
150+ }
151+
152+ // Parent process - close write ends of pipes
153+ close (stdout_pipe[1 ]);
154+ stdout_pipe[1 ] = -1 ;
155+ close (stderr_pipe[1 ]);
156+ stderr_pipe[1 ] = -1 ;
157+
158+ // Read from both pipes simultaneously to avoid deadlock
159+ readFromBothPipes (result.stdout_output , result.stderr_output );
160+
161+ // Close read ends
162+ close (stdout_pipe[0 ]);
163+ stdout_pipe[0 ] = -1 ;
164+ close (stderr_pipe[0 ]);
165+ stderr_pipe[0 ] = -1 ;
166+
167+ // Wait for child process to finish
168+ int status;
169+ waitpid (pid, &status, 0 );
170+
171+ if (WIFEXITED (status)) {
172+ result.exit_code = WEXITSTATUS (status);
173+ }
174+
175+ return result;
176+ }
177+ };
178+
8179void exec (const std::string& cmd, const std::string& err_msg_prefix, const boost::filesystem::path& start_dir,
9180 std::string* output, const std::string& timeout, bool print_output) {
10181 std::string command;
@@ -20,50 +191,29 @@ void exec(const std::string& cmd, const std::string& err_msg_prefix, const boost
20191 if (!timeout.empty ()) {
21192 command += " timeout " + timeout + " " ;
22193 }
23- command += cmd + " 2>&1 " ;
194+ command += cmd;
24195 if (!start_dir.empty ()) {
25196 command = " cd " + start_dir.string () + " && " + command;
26197 }
27198
28199 LOG_DEBUG << " Running: `" << command << " `" ;
29- FILE* pipe = popen (command.c_str (), " r" );
30- if (!pipe) {
31- throw std::runtime_error (" exec: popen() failed!" );
32- }
33- std::string result;
34- try {
35- std::array<char , 128 > buffer_array = {};
36- char * buffer = buffer_array.data ();
37- while (std::fgets (buffer, sizeof (buffer_array), pipe) != nullptr ) {
38- if (print_output) {
39- fputs (buffer, stdout);
40- }
41- result += buffer;
42- }
200+ Process proc;
201+ auto result = proc.execute (command.c_str ());
43202
44- if (output != nullptr ) {
45- *output = result;
46- }
47- } catch (const std::exception& e) {
48- pclose (pipe);
49- throw std::runtime_error (std::string (" exec: " ) + e.what ());
203+ if (result.exit_code == 124 ) {
204+ // `timeout` command return code indicating that a timeout has occurred
205+ throw std::runtime_error (" Timeout occurred while waiting for a child process completion" );
50206 }
207+ LOG_DEBUG << " Command exited with code " << result.exit_code ;
51208
52- int status = pclose (pipe);
53- if (status == -1 ) {
54- throw std::runtime_error (" exec: pclose() failed!" );
209+ if (result.exit_code != EXIT_SUCCESS) {
210+ throw ExecError (err_msg_prefix, cmd, result.stderr_output , result.exit_code );
55211 }
56-
57- int exit_code = WEXITSTATUS (status);
58-
59- if (exit_code == 124 ) {
60- // `timeout` command return code indicating that a timeout has occured
61- throw std::runtime_error (" Timeout occurred while waiting for a child process completion" );
212+ if (output != nullptr ) {
213+ *output = result.stdout_output ;
62214 }
63- LOG_DEBUG << " Command exited with code " << exit_code;
64-
65- if (exit_code != EXIT_SUCCESS) {
66- throw ExecError (err_msg_prefix, cmd, result, exit_code);
215+ if (result.stderr_output .size () > 0 ) {
216+ LOG_DEBUG << " Command stderr: " << result.stderr_output ;
67217 }
68218}
69219
0 commit comments