aboutsummaryrefslogtreecommitdiffstats
path: root/libcoopgamma_query__.c
blob: 706a3cfa7a9101cc1ad52f29503cdb0b173d2a9c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* 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;
}