/* See LICENSE file for copyright and license details. */
#include "common.h"
/**
* Run coopgammad with -q or -qq and return the response
*
* SIGCHLD must not be ignored or blocked
*
* @param method The adjustment method, `NULL` for automatic
* @param site The site, `NULL` for automatic
* @param arg "-q" or "-qq", which shall be passed to coopgammad?
* @return The output of coopgammad, `NULL` on error. This
* will be NUL-terminated and will not contain any
* other `NUL` bytes.
*/
char *
libcoopgamma_query__(const char *restrict method, const char *restrict site, const char *restrict arg)
{
const char *(args[7]) = {COOPGAMMAD, arg};
size_t i = 2, n = 0, size = 0;
int pipe_rw[2] = { -1, -1 };
pid_t pid;
int saved_errno, status;
char *msg = NULL;
ssize_t got;
void *new;
if (method) args[i++] = "-m", args[i++] = method;
if (site) args[i++] = "-s", args[i++] = site;
args[i] = NULL;
if (pipe(pipe_rw) < 0)
goto fail;
switch ((pid = fork())) {
case -1:
goto fail;
case 0:
/* Child */
close(pipe_rw[0]);
if (pipe_rw[1] != STDOUT_FILENO) {
close(STDOUT_FILENO);
if (dup2(pipe_rw[1], STDOUT_FILENO) < 0)
goto fail_child;
close(pipe_rw[1]);
}
#if defined(__GNUC__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wcast-qual"
#endif
execvp(COOPGAMMAD, (char* const*)(args));
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
fail_child:
saved_errno = errno;
perror(NAME_OF_THE_PROCESS);
if (write(STDOUT_FILENO, &saved_errno, sizeof(int)) != sizeof(int))
perror(NAME_OF_THE_PROCESS);
exit(1);
default:
/* Parent */
close(pipe_rw[1]), pipe_rw[1] = -1;
for (;;) {
if (n == size) {
new = realloc(msg, size = (n ? (n << 1) : 256U));
if (!new)
goto fail;
msg = new;
}
got = read(pipe_rw[0], &msg[n], size - n);
if (got < 0) {
if (errno == EINTR)
continue;
goto fail;
} else if (got == 0) {
break;
}
n += (size_t)got;
}
close(pipe_rw[0]), pipe_rw[0] = -1;
if (waitpid(pid, &status, 0) < 0)
goto fail;
if (status) {
errno = EINVAL;
if (n == sizeof(int) && *(int *)msg)
errno = *(int*)msg;
}
break;
}
if (n == size) {
new = realloc(msg, n + 1U);
if (!new)
goto fail;
msg = new;
}
msg[n] = '\0';
if (strchr(msg, '\0') != msg + n) {
errno = EBADMSG;
goto fail;
}
return msg;
fail:
saved_errno = errno;
if (pipe_rw[0] >= 0)
close(pipe_rw[0]);
if (pipe_rw[1] >= 0)
close(pipe_rw[1]);
free(msg);
errno = saved_errno;
return NULL;
}