#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
// available types
#define TYPE_STR 1
#define TYPE_CSTR 2
#define TYPE_INT 3
// lookup type names and format strings
#define TYPE_STR_1 char*
#define TYPE_STR_2 const char*
#define TYPE_STR_3 int
#define SCAN_STR_3 "%d"
#define GET_TYPE_STR_(T) TYPE_STR_##T
#define GET_TYPE_STR(T) GET_TYPE_STR_(T)
#define GET_SCAN_STR_(T) SCAN_STR_##T
#define GET_SCAN_STR(T) GET_SCAN_STR_(T)
// stringify
#define XSTR(s) STR(s)
#define STR(s) #s
// actual user configuration
// required defines: NAME, BINARY, RV, ERROR_RV, ARG1..3
#include "config.h"
// the exported function
GET_TYPE_STR(RV) NAME (
#define NUMARGS 0
#ifdef ARG1
GET_TYPE_STR(ARG1) arg1
#undef NUMARGS
#define NUMARGS 1
#ifdef ARG2
, GET_TYPE_STR(ARG2) arg2
#undef NUMARGS
#define NUMARGS 2
#ifdef ARG3
, GET_TYPE_STR(ARG3) arg3
#undef NUMARGS
#define NUMARGS 3
#endif
#endif
#endif
) {
// create argv to exec
static const char* binary = XSTR(BINARY);
const char* argv[NUMARGS+2];
argv[0] = binary;
argv[NUMARGS+1] = NULL;
#define ARG2STR(i, t) char arg_s_##i[32]; argv[i+1] = arg_s_##i; sprintf(arg_s_##i, GET_SCAN_STR(t), arg##i)
#if NUMARGS > 0
#if ARG1 == TYPE_STR || ARG1 == TYPE_CSTR
argv[1] = arg1;
#else
ARG2STR(1, ARG1);
#endif
#endif
#if NUMARGS > 1
#if ARG2 == TYPE_STR || ARG2 == TYPE_CSTR
argv[2] = arg2;
#else
ARG2STR(2, ARG2);
#endif
#endif
#if NUMARGS > 2
#if ARG3 == TYPE_STR || ARG3 == TYPE_CSTR
argv[3] = arg3;
#else
ARG2STR(3, ARG3);
#endif
#endif
// pipe + fork + exec
int pfd[2];
if (pipe(pfd) == -1) {
return ERROR_RV;
}
const pid_t pid = fork();
if (pid == -1) {
close(pfd[0]);
close(pfd[1]);
return ERROR_RV;
} else if (pid == 0) {
int null = open("/dev/null", O_RDWR);
if (null == -1) raise(SIGTERM); // so we can distinguish real exit codes
if (dup2(null, 0) == -1) raise(SIGTERM);
if (dup2(pfd[1], 1) == -1) raise(SIGTERM);
if (dup2(null, 2) == -1) raise(SIGTERM);
int i;
for (i=3; i<getdtablesize(); ++i) close(i);
execvp(argv[0], (char* const*)argv);
raise(SIGTERM);
exit(1);
} else {
close(pfd[1]);
}
// read from pipe
size_t len = 0;
#if RV == TYPE_CSTR
static char buf[4096];
size_t alen = sizeof(buf);
#else
size_t alen = 32;
char* buf = (char*)malloc(alen);
#endif
while (1) {
ssize_t rv = read(pfd[0], buf+len, alen-len-1);
if (rv == -1) {
if (errno == EINTR) {
continue;
} else { // EPIPE?
alen = 0; // indicates error
break; // send some kill here?
}
} else if (rv == 0) {
break; // eof
} else {
len += rv;
if (len >= alen-1) {
#if RV == TYPE_CSTR
alen = 0;
break;
#else
alen *= 2;
buf = realloc(buf, alen);
#endif
}
}
}
close(pfd[0]);
// wait in any case
int status;
while (1) {
pid_t rv = waitpid(pid, &status, 0);
if (rv == -1) {
if (errno == EINTR) {
continue;
} else {
#if RV != TYPE_CSTR
free(buf);
#endif
return ERROR_RV;
}
} else {
break;
}
}
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
#if RV != TYPE_CSTR
free(buf);
#endif
return ERROR_RV;
}
// parse into rv
if (!alen) {
#if RV != TYPE_CSTR
free(buf);
#endif
return ERROR_RV;
}
buf[len] = '\0';
#if RV == TYPE_STR || RV == TYPE_CSTR
return buf;
#else
GET_TYPE_STR(RV) rv;
if (sscanf(buf, GET_SCAN_STR(RV), &rv) != 1) {
rv = ERROR_RV;
}
free(buf);
return rv;
#endif
}