#include "handler.hpp"
#include <sys/wait.h>
#include <fcntl.h>
// execs and writes stdout either to given fd or buf
// returns <0 upon error, 0 upon fd-write success or no data, >0 for buffer length
int handler(const char* bin, const char* arg, int wd, int outfd, char*& outbuf) {
outbuf = NULL;
// get common null fd - XXX: will be leaked
static int nullfd = -1;
if (nullfd == -1) {
nullfd = open("/dev/null", O_RDWR); // XXX: additional leak upon mt race
if (nullfd == -1) {
return -errno;
}
}
// create pipe in case we don't already have an fd to write to
int infd = -1;
if (outfd == -1) {
int fds[2];
if (pipe(fds) == -1) {
return -errno;
}
infd = fds[0];
outfd = fds[1];
}
// fork
const pid_t pid = fork();
if (pid == -1) {
return -errno;
}
// child: exec
if (pid == 0) {
dup2(nullfd, STDIN_FILENO);
dup2(nullfd, STDERR_FILENO);
dup2(outfd, STDOUT_FILENO);
if (fchdir(wd) == -1) { // not in the mountpoint but the original one
_exit(1);
}
for (int i=3; i<getdtablesize(); ++i) {
close(i);
}
const char* argv[] = {
bin, arg, NULL
};
(void)execvpe(bin, (char* const*)argv, NULL);
_exit(1);
}
LOG_DEBUG("spawned #%d: '%s' '%s'", pid, bin, arg);
// read from pipe into given buffer, if needed
int rv = 0;
if (infd != -1) {
(void)close(outfd);
size_t len = 0;
size_t alloc_len = 0;
while (true) {
if (alloc_len - len < 4096) {
alloc_len += 4096;
outbuf = (char*)realloc(outbuf, alloc_len);
}
ssize_t r = read(infd, outbuf+len, alloc_len-len-1);
int e = errno;
errno = e;
if (r == -1) {
rv = -errno;
break;
} else if (r == 0) { // eof -> can wait
rv = len;
outbuf[len] = '\0';
//LOG("> %d:'%s'", len, outbuf);
break;
} else { // go on
len += (size_t)r;
}
}
(void)close(infd);
}
// wait
int status;
if (waitpid(pid, &status, 0) == -1) {
rv = -errno;
} else if (WIFEXITED(status)) {
rv = (WEXITSTATUS(status) == 0)? rv: -EFAULT;
} else if (WIFSIGNALED(status)) {
rv = -EINTR;
} else {
rv = -EINVAL; // ??
}
// cleanup & done
if (rv < 0 && outbuf) {
free(outbuf);
outbuf = NULL;
}
if (rv < 0) {
LOG_ERR(-rv, "'%s' done", bin);
} else {
LOG_DEBUG_ERR(0, "'%s' done: %d", bin, rv);
}
return rv;
}