Generated on for Gecode by doxygen 1.15.0
blackbox-process-posix.cpp
Go to the documentation of this file.
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 * Main authors:
4 * Jip J. Dekker <jip.dekker@monash.edu>
5 *
6 * Contributing authors:
7 * Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
8 *
9 * Copyright:
10 * Jip J. Dekker, 2026
11 */
13#include <gecode/flatzinc.hh>
14
15#if defined(GECODE_HAS_POSIX_BLACKBOX_EXEC) && !defined(_WIN32)
16
17#include <cerrno>
18#include <cstdio>
19#include <memory>
20#include <spawn.h>
21#include <fcntl.h>
22#include <pthread.h>
23#include <signal.h>
24#include <sys/socket.h>
25#include <sys/types.h>
26#include <sys/wait.h>
27#include <time.h>
28#include <unistd.h>
29
30extern char **environ;
31
32namespace Gecode { namespace FlatZinc {
33namespace {
34
35const size_t max_exec_response_size = 1024 * 1024;
36
37int
38set_cloexec(int fd) {
39 int flags = fcntl(fd, F_GETFD);
40 if (flags == -1) {
41 return -1;
42 }
43 return fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
44}
45
46int
47dup_cloexec(int fd, int min_fd) {
48 int nfd;
49#ifdef F_DUPFD_CLOEXEC
50 nfd = fcntl(fd, F_DUPFD_CLOEXEC, min_fd);
51 if (nfd != -1) {
52 return nfd;
53 }
54 if (errno != EINVAL) {
55 return -1;
56 }
57#endif
58 nfd = fcntl(fd, F_DUPFD, min_fd);
59 if (nfd == -1) {
60 return -1;
61 }
62 if (set_cloexec(nfd) != 0) {
63 int e = errno;
64 ::close(nfd);
65 errno = e;
66 return -1;
67 }
68 return nfd;
69}
70
71int
72move_from_standard_fd(int fd) {
73 if (fd > STDERR_FILENO) {
74 return fd;
75 }
76 int nfd = dup_cloexec(fd, STDERR_FILENO + 1);
77 if (nfd == -1) {
78 return -1;
79 }
80 ::close(fd);
81 return nfd;
82}
83
84class FileDescriptor {
85private:
86 int fd;
87public:
88 explicit FileDescriptor(int fd0=-1) : fd(fd0) {}
89 ~FileDescriptor(void) { reset(); }
90
91 int get(void) const { return fd; }
92 int release(void) {
93 int fd0 = fd;
94 fd = -1;
95 return fd0;
96 }
97 void reset(int fd0=-1) {
98 if (fd != -1) {
99 ::close(fd);
100 }
101 fd = fd0;
102 }
103};
104
105int
106move_away_from_standard_fd(FileDescriptor &fd) {
107 int old = fd.release();
108 int nfd = move_from_standard_fd(old);
109 if (nfd == -1) {
110 fd.reset(old);
111 } else {
112 fd.reset(nfd);
113 }
114 return nfd;
115}
116
117class SpawnFileActions {
118private:
119 posix_spawn_file_actions_t actions;
120 bool initialized;
121public:
122 SpawnFileActions(void) : initialized(false) {}
123 ~SpawnFileActions(void) {
124 if (initialized) {
125 posix_spawn_file_actions_destroy(&actions);
126 }
127 }
128
129 int init(void) {
130 int err = posix_spawn_file_actions_init(&actions);
131 initialized = err == 0;
132 return err;
133 }
134 posix_spawn_file_actions_t *get(void) { return &actions; }
135};
136
137class SpawnAttributes {
138private:
139 posix_spawnattr_t attr;
140 bool initialized;
141public:
142 SpawnAttributes(void) : initialized(false) {}
143 ~SpawnAttributes(void) {
144 if (initialized) {
145 posix_spawnattr_destroy(&attr);
146 }
147 }
148
149 int init(void) {
150 int err = posix_spawnattr_init(&attr);
151 initialized = err == 0;
152 return err;
153 }
154 posix_spawnattr_t *get(void) { return &attr; }
155};
156
157int
158create_socketpair(int sv[2]) {
159#ifdef SOCK_CLOEXEC
160 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) == 0) {
161 return 0;
162 }
163 if (errno != EINVAL) {
164 return -1;
165 }
166#endif
167 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) != 0) {
168 return -1;
169 }
170 if ((set_cloexec(sv[0]) != 0) || (set_cloexec(sv[1]) != 0)) {
171 int e = errno;
172 ::close(sv[0]);
173 ::close(sv[1]);
174 errno = e;
175 return -1;
176 }
177 return 0;
178}
179
180ssize_t
181send_no_sigpipe(int fd, const char *data, size_t size) {
182#ifdef MSG_NOSIGNAL
183 return send(fd, data, size, MSG_NOSIGNAL);
184#else
185#ifdef SO_NOSIGPIPE
186 return send(fd, data, size, 0);
187#else
188 sigset_t block;
189 sigset_t old;
190 sigset_t pending;
191 sigemptyset(&block);
192 sigaddset(&block, SIGPIPE);
193 bool blocked = false;
194 bool was_pending = false;
195 if (pthread_sigmask(SIG_BLOCK, &block, &old) == 0) {
196 blocked = true;
197 if (sigpending(&pending) == 0) {
198 was_pending = sigismember(&pending, SIGPIPE) == 1;
199 }
200 }
201 ssize_t n = send(fd, data, size, 0);
202 if ((n == -1) && (errno == EPIPE) && !was_pending) {
203 const struct timespec timeout = {0, 0};
204 sigtimedwait(&block, NULL, &timeout);
205 }
206 if (blocked) {
207 pthread_sigmask(SIG_SETMASK, &old, NULL);
208 }
209 return n;
210#endif
211#endif
212}
213class PosixProcessSession : public BlackBoxProcessSession {
214protected:
215 pid_t child;
216 int pipe_send;
217 FILE *file_receive;
218
219 static std::string last_error(const std::string &prefix) {
220 return prefix + " (errno " + std::to_string(errno) + ")";
221 }
222
223 static void sleep_grace_period(void) {
224 struct timespec remaining = {0, 10000000};
225 while ((nanosleep(&remaining, &remaining) == -1) && (errno == EINTR)) {}
226 }
227
228 static bool child_exited(pid_t pid) {
229 siginfo_t info;
230 do {
231 info.si_pid = 0;
232 if (waitid(P_PID, pid, &info, WEXITED | WNOHANG | WNOWAIT) == 0) {
233 return info.si_pid != 0;
234 }
235 } while (errno == EINTR);
236 return false;
237 }
238
239 static void signal_group(pid_t pid, int signal) {
240 if ((kill(-pid, signal) == -1) && (errno == ESRCH)) {
241 return;
242 }
243 }
244
245 static void wait_group(pid_t pid, int attempts) {
246 for (int i = 0; i < attempts; i++) {
247 if ((kill(-pid, 0) == -1) && (errno == ESRCH)) {
248 return;
249 }
250 if (child_exited(pid)) {
251 return;
252 }
253 sleep_grace_period();
254 }
255 }
256
257 static void terminate_child(pid_t pid) {
258 if (pid <= 0) {
259 return;
260 }
261 int status = 0;
262 // Keep the child unreaped until the group has received both signals.
263 signal_group(pid, SIGTERM);
264 wait_group(pid, 100);
265 signal_group(pid, SIGKILL);
266 do {
267 if (waitpid(pid, &status, 0) != -1) {
268 return;
269 }
270 } while (errno == EINTR);
271 }
272
273 static void check_sigchld(void) {
274 struct sigaction action;
275 if (sigaction(SIGCHLD, NULL, &action) != 0) {
276 throw Error("BlackBoxExec", last_error("SIGCHLD query failed"));
277 }
278 if ((action.sa_handler != SIG_DFL)
279#ifdef SA_NOCLDWAIT
280 || (action.sa_flags & SA_NOCLDWAIT)
281#endif
282 ) {
283 throw Error("BlackBoxExec",
284 "Cannot start a blackbox process unless SIGCHLD uses "
285 "SIG_DFL without SA_NOCLDWAIT");
286 }
287 }
288
289 void open_posix(const std::string &program,
290 const std::vector<std::string> &args);
291 void close_posix(void);
292
293public:
294 PosixProcessSession(const std::string &program, const std::vector<std::string> &args)
295 : child(-1), pipe_send(-1), file_receive(NULL)
296 {
297 open_posix(program, args);
298 }
299
300 ~PosixProcessSession(void) { close(); }
301
302 std::string exchange(const std::string &out_buf) {
303 const char *p = out_buf.c_str();
304 size_t remaining = out_buf.size();
305 while (remaining > 0) {
306 ssize_t n = send_no_sigpipe(pipe_send, p, remaining);
307 if (n < 0) {
308 if (errno == EINTR) {
309 continue;
310 }
311 throw Error("BlackBoxExec",
312 "Writing blackbox process input failed with errno " +
313 std::to_string(errno));
314 }
315 if (n == 0) {
316 throw Error("BlackBoxExec",
317 "Writing blackbox process input wrote zero bytes");
318 }
319 p += n;
320 remaining -= static_cast<size_t>(n);
321 }
322
323 std::string in_buffer;
324 while (true) {
325 errno = 0;
326 int ch = fgetc(file_receive);
327 if (ch == EOF) {
328 if (feof(file_receive)) {
329 throw Error("BlackBoxExec",
330 "Blackbox process provided an incomplete response");
331 }
332 int err = errno;
333 if (err == EINTR) {
334 clearerr(file_receive);
335 continue;
336 }
337 throw Error("BlackBoxExec",
338 std::string("Reading blackbox process output from pipe "
339 "failed with errno ") +
340 std::to_string(err));
341 }
342 in_buffer += static_cast<char>(ch);
343 if (in_buffer.size() > max_exec_response_size) {
344 throw Error("BlackBoxExec",
345 "Blackbox process response exceeds the size limit");
346 }
347 if (ch == '\n') {
348 break;
349 }
350 }
351 return in_buffer;
352 }
353
354 void close(void) {
355 close_posix();
356 }
357};
358
359void
360PosixProcessSession::open_posix(const std::string& program,
361 const std::vector<std::string>& args) {
362 const int READ = 0;
363 const int WRITE = 1;
364
365 std::vector<char *> argv;
366 argv.reserve(args.size() + 2);
367 argv.push_back(const_cast<char *>(program.c_str()));
368 for (const std::string &a : args) {
369 argv.push_back(const_cast<char *>(a.c_str()));
370 }
371 argv.push_back(nullptr);
372
373 check_sigchld();
374
375 FileDescriptor child_in[2];
376 FileDescriptor child_out[2];
377 int fds[2];
378 if (create_socketpair(fds) != 0) {
379 throw Error("BlackBoxExec", last_error("stdin socket creation failed"));
380 }
381 child_in[READ].reset(fds[READ]);
382 child_in[WRITE].reset(fds[WRITE]);
383 if (create_socketpair(fds) != 0) {
384 throw Error("BlackBoxExec", last_error("stdout socket creation failed"));
385 }
386 child_out[READ].reset(fds[READ]);
387 child_out[WRITE].reset(fds[WRITE]);
388 FileDescriptor *session_fds[] = {
389 &child_in[READ], &child_in[WRITE],
390 &child_out[READ], &child_out[WRITE]
391 };
392 for (FileDescriptor *fd : session_fds) {
393 if (move_away_from_standard_fd(*fd) == -1) {
394 throw Error("BlackBoxExec",
395 last_error("moving session descriptors away from stdio "
396 "failed"));
397 }
398 }
399
400 SpawnFileActions actions;
401 int err = actions.init();
402 if (err != 0) {
403 errno = err;
404 throw Error("BlackBoxExec", last_error("spawn file action init failed"));
405 }
406
407 SpawnAttributes attr;
408 err = attr.init();
409 if (err != 0) {
410 errno = err;
411 throw Error("BlackBoxExec", last_error("spawn attribute init failed"));
412 }
413
414 err = posix_spawnattr_setpgroup(attr.get(), 0);
415 if (err == 0) {
416 short flags = POSIX_SPAWN_SETPGROUP;
417#if defined(GECODE_HAS_POSIX_SPAWN_CLOEXEC_DEFAULT) && \
418 defined(GECODE_HAS_POSIX_SPAWN_ADDINHERIT_NP)
419 flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
420#endif
421 err = posix_spawnattr_setflags(attr.get(), flags);
422 }
423 if (err == 0) {
424 err = posix_spawn_file_actions_adddup2(actions.get(),
425 child_in[READ].get(),
426 STDIN_FILENO);
427 }
428 if (err == 0) {
429 err = posix_spawn_file_actions_adddup2(actions.get(),
430 child_out[WRITE].get(),
431 STDOUT_FILENO);
432 }
433#if defined(GECODE_HAS_POSIX_SPAWN_CLOEXEC_DEFAULT) && \
434 defined(GECODE_HAS_POSIX_SPAWN_ADDINHERIT_NP)
435 if (err == 0) {
436 err = posix_spawn_file_actions_addinherit_np(actions.get(),
437 STDERR_FILENO);
438 }
439#elif defined(GECODE_HAS_POSIX_SPAWN_ADDCLOSEFROM_NP)
440 if (err == 0) {
441 err = posix_spawn_file_actions_addclosefrom_np(actions.get(),
442 STDERR_FILENO + 1);
443 }
444#endif
445 if (err == 0) {
446 err = posix_spawnp(&child, program.c_str(), actions.get(), attr.get(),
447 argv.data(), environ);
448 }
449 if (err != 0) {
450 child = -1;
451 errno = err;
452 throw Error("BlackBoxExec", last_error("starting blackbox process failed"));
453 }
454
455 child_in[READ].reset();
456 child_out[WRITE].reset();
457
458#ifdef SO_NOSIGPIPE
459 int nosigpipe = 1;
460 if (setsockopt(child_in[WRITE].get(), SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe,
461 sizeof(nosigpipe)) != 0) {
462 int e = errno;
463 terminate_child(child);
464 child = -1;
465 errno = e;
466 throw Error("BlackBoxExec", last_error("SO_NOSIGPIPE setup failed"));
467 }
468#endif
469 FILE *receive = fdopen(child_out[READ].get(), "r");
470 if (receive == NULL) {
471 int e = errno;
472 terminate_child(child);
473 child = -1;
474 errno = e;
475 throw Error("BlackBoxExec", last_error("fdopen failed"));
476 }
477 file_receive = receive;
478 child_out[READ].release();
479 pipe_send = child_in[WRITE].release();
480}
481
482void
483PosixProcessSession::close_posix(void) {
484 if (pipe_send != -1) {
485 ::close(pipe_send);
486 pipe_send = -1;
487 }
488 if (file_receive != NULL) {
489 fclose(file_receive);
490 file_receive = NULL;
491 }
492 if (child > 0) {
493 terminate_child(child);
494 child = -1;
495 }
496}
497
498} // namespace
499
501create_blackbox_process(const std::string& program,
502 const std::vector<std::string>& args) {
503 return new PosixProcessSession(program, args);
504}
505
506}}
507#endif
508
509// STATISTICS: flatzinc-other
Platform process session used by the executable blackbox backend.
Exception class for FlatZinc errors
Definition flatzinc.hh:727
Interpreter for the FlatZinc language.
BlackBoxProcessSession * create_blackbox_process(const std::string &, const std::vector< std::string > &)
Create the process implementation selected for the target platform.
void reset(void)
Reset all failpoint state.
void exchange(Type &a, Type &b, Less &less)
Exchange elements according to order.
Definition sort.hpp:42
Gecode toplevel namespace
Gecode::FloatVal a(-8, 5)
Gecode::IntArgs i({1, 2, 3, 4})