/*
* Process spawn functions
* Copyright (C) 2008 Andreas Öman
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "tvheadend.h"
#include "tvhpoll.h"
#include "file.h"
#include "spawn.h"
#if ENABLE_ANDROID
#define WIFCONTINUED(s) ((s) == 0xffff)
#endif
extern char **environ;
pthread_mutex_t spawn_mutex = PTHREAD_MUTEX_INITIALIZER;
static LIST_HEAD(, spawn) spawns;
static char *spawn_info_buf = NULL;
static char *spawn_error_buf = NULL;
static th_pipe_t spawn_pipe_info;
static th_pipe_t spawn_pipe_error;
static pthread_t spawn_pipe_tid;
static int spawn_pipe_running;
typedef struct spawn {
LIST_ENTRY(spawn) link;
pid_t pid;
const char *name;
} spawn_t;
/*
*
*/
#define SPAWN_PIPE_READ_SIZE 4096
static void
spawn_pipe_read( th_pipe_t *p, char **_buf, int level )
{
char *buf = *_buf, *s;
size_t len;
int r;
if (buf == NULL) {
buf = malloc(SPAWN_PIPE_READ_SIZE);
buf[0] = '\0';
buf[SPAWN_PIPE_READ_SIZE - 1] = 0;
*_buf = buf;
}
while (1) {
len = strlen(buf);
r = read(p->rd, buf + len, SPAWN_PIPE_READ_SIZE - 1 - len);
if (r < 1) {
if (errno == EAGAIN)
break;
if (ERRNO_AGAIN(errno))
continue;
break;
}
buf[len + r] = '\0';
tvhlog_hexdump("spawn", buf + len, r);
while (1) {
s = buf;
while (*s && *s != '\n' && *s != '\r')
s++;
if (*s == '\0')
break;
*s++ = '\0';
if (buf[0])
tvhlog(level, "spawn", "%s", buf);
memmove(buf, s, strlen(s) + 1);
}
if (strlen(buf) == SPAWN_PIPE_READ_SIZE - 1) {
tvherror("spawn", "pipe buffer full");
buf[0] = '\0';
}
}
}
static void *
spawn_pipe_thread(void *aux)
{
tvhpoll_event_t ev[2];
tvhpoll_t *efd = tvhpoll_create(2);
int nfds;
memset(ev, 0, sizeof(ev));
ev[0].events = TVHPOLL_IN;
ev[0].fd = spawn_pipe_info.rd;
ev[0].data.ptr = &spawn_pipe_info;
ev[1].events = TVHPOLL_IN;
ev[1].fd = spawn_pipe_error.rd;
ev[1].data.ptr = &spawn_pipe_error;
tvhpoll_add(efd, ev, 2);
while (spawn_pipe_running) {
nfds = tvhpoll_wait(efd, ev, 2, 500);
if (nfds > 0) {
spawn_pipe_read(&spawn_pipe_info, &spawn_info_buf, LOG_INFO);
spawn_pipe_read(&spawn_pipe_error, &spawn_error_buf, LOG_ERR);
}
spawn_reaper();
}
tvhpoll_destroy(efd);
return NULL;
}
static void
spawn_pipe_write( th_pipe_t *p, const char *fmt, va_list ap )
{
char buf[512], *s = buf;
int r;
vsnprintf(buf, sizeof(buf), fmt, ap);
while (*s) {
r = write(p->wr, s, strlen(s));
if (r < 0) {
if (errno == EAGAIN)
break;
if (ERRNO_AGAIN(errno))
continue;
break;
}
if (!r)
break;
s += r;
}
}
void
spawn_info( const char *fmt, ... )
{
va_list ap;
va_start(ap, fmt);
spawn_pipe_write(&spawn_pipe_info, fmt, ap);
va_end(ap);
}
void
spawn_error( const char *fmt, ... )
{
va_list ap;
va_start(ap, fmt);
spawn_pipe_write(&spawn_pipe_error, fmt, ap);
va_end(ap);
}
/*
* Search PATH for executable
*/
int
find_exec ( const char *name, char *out, size_t len )
{
int ret = 0;
char bin[512];
char *path, *tmp, *tmp2 = NULL;
DIR *dir;
struct dirent *de;
struct stat st;
if (name[0] == '/') {
if (lstat(name, &st)) return 0;
if (!S_ISREG(st.st_mode) || !(st.st_mode & S_IEXEC)) return 0;
strncpy(out, name, len);
out[len-1] = '\0';
return 1;
}
if (!(path = getenv("PATH"))) return 0;
path = strdup(path);
tmp = strtok_r(path, ":", &tmp2);
while (tmp && !ret) {
if ((dir = opendir(tmp))) {
while ((de = readdir(dir))) {
if (strstr(de->d_name, name) != de->d_name) continue;
snprintf(bin, sizeof(bin), "%s/%s", tmp, de->d_name);
if (lstat(bin, &st)) continue;
if (!S_ISREG(st.st_mode) || !(st.st_mode & S_IEXEC)) continue;
strncpy(out, bin, len);
out[len-1] = '\0';
ret = 1;
break;
}
closedir(dir);
}
tmp = strtok_r(NULL, ":", &tmp2);
}
free(path);
return ret;
}
/**
* Reap one child
*/
int
spawn_reap(char *stxt, size_t stxtlen)
{
pid_t pid;
int status, res;
spawn_t *s;
pid = waitpid(-1, &status, WNOHANG);
if(pid < 1)
return -EAGAIN;
pthread_mutex_lock(&spawn_mutex);
LIST_FOREACH(s, &spawns, link)
if(s->pid == pid)
break;
res = -EIO;
if (WIFEXITED(status)) {
res = WEXITSTATUS(status);
if (stxt)
snprintf(stxt, stxtlen, "exited, status=%d", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
if (stxt)
snprintf(stxt, stxtlen, "killed by signal %d, "
"stopped by signal %d",
WTERMSIG(status),
WSTOPSIG(status));
} else if (WIFCONTINUED(status)) {
if (stxt)
snprintf(stxt, stxtlen, "continued");
} else {
if (stxt)
snprintf(stxt, stxtlen, "unknown status");
}
if(s != NULL) {
LIST_REMOVE(s, link);
free((void *)s->name);
free(s);
}
pthread_mutex_unlock(&spawn_mutex);
return res;
}
/**
* The reaper is called once a second to finish of any pending spawns
*/
void
spawn_reaper(void)
{
while (spawn_reap(NULL, 0) != -EAGAIN) ;
}
/**
* Enqueue a spawn on the pending spawn list
*/
static spawn_t *
spawn_enq(const char *name, int pid)
{
spawn_t *s = calloc(1, sizeof(spawn_t));
s->name = strdup(name);
s->pid = pid;
pthread_mutex_lock(&spawn_mutex);
LIST_INSERT_HEAD(&spawns, s, link);
pthread_mutex_unlock(&spawn_mutex);
return s;
}
/**
* Execute the given program and return its standard output as file-descriptor (pipe).
*/
int
spawn_and_give_stdout(const char *prog, char *argv[], int *rd, int redir_stderr)
{
pid_t p;
int fd[2], f, maxfd;
char bin[256];
const char *local_argv[2] = { NULL, NULL };
if (*prog != '/' && *prog != '.') {
if (!find_exec(prog, bin, sizeof(bin))) return -1;
prog = bin;
}
if (!argv) argv = (void *)local_argv;
if (!argv[0]) argv[0] = (char*)prog;
maxfd = sysconf(_SC_OPEN_MAX);
pthread_mutex_lock(&fork_lock);
if(pipe(fd) == -1) {
pthread_mutex_unlock(&fork_lock);
return -1;
}
p = fork();
if(p == -1) {
pthread_mutex_unlock(&fork_lock);
tvherror("spawn", "Unable to fork() for \"%s\" -- %s",
prog, strerror(errno));
return -1;
}
if(p == 0) {
close(0);
close(2);
close(fd[0]);
dup2(fd[1], 1);
close(fd[1]);
f = open("/dev/null", O_RDWR);
if(f == -1) {
spawn_error("pid %d cannot open /dev/null for redirect %s -- %s",
getpid(), prog, strerror(errno));
exit(1);
}
dup2(f, 0);
dup2(redir_stderr ? spawn_pipe_error.wr : f, 2);
close(f);
spawn_info("Executing \"%s\"\n", prog);
for (f = 3; f < maxfd; f++)
close(f);
execve(prog, argv, environ);
spawn_error("pid %d cannot execute %s -- %s\n",
getpid(), prog, strerror(errno));
exit(1);
}
pthread_mutex_unlock(&fork_lock);
spawn_enq(prog, p);
close(fd[1]);
*rd = fd[0];
return 0;
}
/**
* Execute the given program with arguments
*
* *outp will point to the allocated buffer
* The function will return the size of the buffer
*/
int
spawnv(const char *prog, char *argv[])
{
pid_t p;
char bin[256];
const char *local_argv[2] = { NULL, NULL };
if (*prog != '/' && *prog != '.') {
if (!find_exec(prog, bin, sizeof(bin))) return -1;
prog = bin;
}
if(!argv) argv = (void *)local_argv;
if (!argv[0]) argv[0] = (char*)prog;
pthread_mutex_lock(&fork_lock);
p = fork();
if(p == -1) {
pthread_mutex_unlock(&fork_lock);
tvherror("spawn", "Unable to fork() for \"%s\" -- %s",
prog, strerror(errno));
return -1;
}
if(p == 0) {
close(0);
close(2);
spawn_info("Executing \"%s\"\n", prog);
execve(prog, argv, environ);
spawn_error("pid %d cannot execute %s -- %s\n",
getpid(), prog, strerror(errno));
close(1);
exit(1);
}
pthread_mutex_unlock(&fork_lock);
spawn_enq(prog, p);
return 0;
}
/*
*
*/
void spawn_init(void)
{
tvh_pipe(O_NONBLOCK, &spawn_pipe_info);
tvh_pipe(O_NONBLOCK, &spawn_pipe_error);
spawn_pipe_running = 1;
pthread_create(&spawn_pipe_tid, NULL, spawn_pipe_thread, NULL);
}
void spawn_done(void)
{
spawn_pipe_running = 0;
pthread_kill(spawn_pipe_tid, SIGTERM);
pthread_join(spawn_pipe_tid, NULL);
tvh_pipe_close(&spawn_pipe_error);
tvh_pipe_close(&spawn_pipe_info);
free(spawn_error_buf);
free(spawn_info_buf);
}