torrent-stats/process.c

218 lines
5.3 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <termios.h>
#include <pty.h>
#include <fcntl.h>
#include "list.h"
#include "logging.h"
#include "process.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
static LIST_HEAD(child_proc_list);
struct child_process * childproc_alloc(char *const argv[], const char *pwd)
{
/* count args */
int cnt = 0;
while (argv[cnt++] != NULL);
/* alloc struct child_process and array of char **argv */
int size = sizeof(struct child_process) + sizeof(char *) * cnt;
struct child_process *child = malloc(size);
if (child == NULL) {
log_print(LOG_ERROR, "create_child_process(): malloc()");
return NULL;
}
memset(child, 0, sizeof(struct child_process));
/* argv points to first byte after struct child_process */
child->argv = (char **)(child +1);
int i;
for (i = 0; i < ARRAY_SIZE(child->fd); i++)
child->fd[i] = -1;
/* copy argv */
for (i = 0; i < cnt; i++)
child->argv[i] = (argv[i] != NULL) ? strdup(argv[i]) : NULL;
/* copy pwd */
if (pwd != NULL)
child->pwd = strdup(pwd);
return child;
}
int childproc_free(struct child_process *child)
{
/* child already running, return error */
if (child->pid != 0) {
log_print(LOG_ERROR, "childproc_free(): process [pid:%d] already running", child->pid);
return -1;
}
int i;
for (i = 0; child->argv[i] != NULL; i++)
free(child->argv[i]);
if (child->pwd != NULL)
free(child->pwd);
free(child);
return 0;
}
pid_t childproc_fork(struct child_process *child, void (*exit_cb)(struct child_process *child, int exit_code, void *privdata), void *privdata)
{
struct stat stat_buf;
if (stat(child->argv[0], &stat_buf) != 0) {
log_print(LOG_ERROR, "childproc_fork(): stat()");
return -1;
/* not a regular file, or not executable */
} else if (!S_ISREG(stat_buf.st_mode) || !(stat_buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
log_print(LOG_ERROR, "childproc_fork(): stat()");
return -1;
}
/* exit callback */
child->exit_cb = exit_cb;
child->privdata = privdata;
/*
* fd[0] - if -1 pipe() and dup2() read-side as child stdin, store write-side for parent
* else dup2() this fd as child stdin
* fd[1] - if -1 pipe() and dup2() write-side as child stdout, store read-side for parent
* else dup2() this fd as child stdout
* fd[2] - if -1 pipe() and dup2() write-side as child stderr, store read-side for parent
* else dup2() this fd as child stderr
*/
int child_pipes[3][2];
if (child->fd[STDIN_FILENO] == -1) {
if (pipe(child_pipes[STDIN_FILENO]) < 0) {
log_print(LOG_ERROR, "childproc_fork(): pipe(STDIN_FILENO)");
return -1;
}
} else {
child_pipes[STDIN_FILENO][0] = child->fd[STDIN_FILENO];
child_pipes[STDIN_FILENO][1] = -1;
}
if (child->fd[STDOUT_FILENO] == -1) {
if (pipe(child_pipes[STDOUT_FILENO]) < 0) {
log_print(LOG_ERROR, "childproc_fork(): pipe(STDOUT_FILENO)");
return -1;
}
} else {
child_pipes[STDOUT_FILENO][0] = -1;
child_pipes[STDOUT_FILENO][1] = child->fd[STDOUT_FILENO];
}
if (child->fd[STDERR_FILENO] == -1) {
if (pipe(child_pipes[STDERR_FILENO]) < 0) {
log_print(LOG_ERROR, "childproc_fork(): pipe(STDERR_FILENO)");
return -1;
}
} else {
child_pipes[STDERR_FILENO][0] = -1;
child_pipes[STDERR_FILENO][1] = child->fd[STDERR_FILENO];
}
/* add this to list now, so sigchld_handler can find it */
list_add_tail(&child->list, &child_proc_list);
child->pid = fork();
if (child->pid == 0) { /* child */
close(child_pipes[STDIN_FILENO][1]);
close(child_pipes[STDOUT_FILENO][0]);
close(child_pipes[STDERR_FILENO][0]);
if (dup2(child_pipes[STDIN_FILENO][0], STDIN_FILENO) < 0) {
perror("dup2(STDIN_FILENO)");
exit(1);
}
if (dup2(child_pipes[STDOUT_FILENO][1], STDOUT_FILENO) < 0) {
perror("dup2(STDOUT_FILENO)");
exit(1);
}
if (dup2(child_pipes[STDERR_FILENO][1], STDERR_FILENO) < 0) {
perror("dup2(STDERR_FILENO)");
exit(1);
}
close(child_pipes[STDIN_FILENO][0]);
close(child_pipes[STDOUT_FILENO][1]);
close(child_pipes[STDERR_FILENO][1]);
if (child->pwd != NULL && chdir(child->pwd) < 0) {
perror("chdir()");
exit(1);
}
execv(child->argv[0], child->argv);
perror("execv()");
exit(1);
} else if (child->pid < 0) { /* fork error */
log_print(LOG_ERROR, "childproc_fork(): fork()");
return -1;
} else { /* parent */
if (child->fd[STDIN_FILENO] == -1) {
close(child_pipes[STDIN_FILENO][0]);
child->fd[STDIN_FILENO] = child_pipes[STDIN_FILENO][1];
}
if (child->fd[STDOUT_FILENO] == -1) {
close(child_pipes[STDOUT_FILENO][1]);
child->fd[STDOUT_FILENO] = child_pipes[STDOUT_FILENO][0];
}
if (child->fd[STDERR_FILENO] == -1) {
close(child_pipes[STDERR_FILENO][1]);
child->fd[STDERR_FILENO] = child_pipes[STDERR_FILENO][0];
}
return child->pid;
}
}
void childproc_cleanup(void)
{
struct child_process *child, *tmp;
list_for_each_entry_safe(child, tmp, &child_proc_list, list) {
int status = 0;
int ret = waitpid(child->pid, &status, WNOHANG);
if (ret == -1) {
log_print(LOG_WARN, "childproc_cleanup(): waitpid(%d)", child->pid);
continue;
} else if (ret != 0 && WIFEXITED(status)) {
list_del(&child->list);
if (child->exit_cb != NULL)
child->exit_cb(child, WEXITSTATUS(status), child->privdata);
child->pid = 0;
childproc_free(child);
}
}
}