From db60fd0122ec26a7ff354c395590abf919274181 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Mon, 17 Jul 2017 15:19:43 +0300 Subject: Initial commit --- Makefile | 57 +++++++ runcap.c | 527 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ runcap.h | 69 +++++++++ 3 files changed, 653 insertions(+) create mode 100644 Makefile create mode 100644 runcap.c create mode 100644 runcap.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0cec8a --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +# runcap - run program and capture its output +# Copyright (C) 2017 Sergey Poznyakoff +# +# Runcap 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. +# +# Runcap 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 Runcap. If not, see . + +PREFIX = /usr +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man + +MAN2DIR = $(MANDIR)/man2 + +# The install program. Use cp(1) if not available. +INSTALL = install +# Program to make directory hierarchy. +MKHIER = install -d + +# Compiler options +O = -ggdb -Wall + +SOURCES = runcap.c +OBJECTS = $(SOURCES:.c=.o) +HEADERS = runcap.h + +CFLAGS = $(O) +LDFLAGS = +ARFLAGS = cru + +all: libruncap.a + +libruncap.a: $(OBJECTS) + ar $(ARFLAGS) libruncap.a $(OBJECTS) + ranlib libruncap.a + +install: install-lib install-headers install-man + +install-lib: libruncap.a + $(MKHIER) $(DESTDIR)$(LIBDIR) + $(INSTALL) libruncap.a $(DESTDIR)$(LIBDIR) + +install-headers: runcap.h + $(MKHIER) $(DESTDIR)$(INCLUDEDIR) + $(INSTALL) runcap.h $(DESTDIR)$(INCLUDEDIR) + +install-man:; +# $(MKHIER) $(DESTDIR)$(MAN2DIR) +# $(INSTALL) runcap.2 $(DESTDIR)$(MAN2DIR) diff --git a/runcap.c b/runcap.c new file mode 100644 index 0000000..648fd84 --- /dev/null +++ b/runcap.c @@ -0,0 +1,527 @@ +/* runcap - run program and capture its output + Copyright (C) 2017 Sergey Poznyakoff + + Runcap 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. + + Runcap 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 Runcap. If not, see . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "runcap.h" + +static int +filecapture_alloc(struct filecapture *fc, size_t size) +{ + if (size) { + fc->fc_base = malloc(size); + if (!fc->fc_base) + return -1; + } else + fc->fc_base = NULL; + fc->fc_size = size; + fc->fc_leng = 0; + fc->fc_level = 0; + fc->fc_nlines = 0; + fc->fc_linestart = 0; + fc->fc_tmpfd = -1; + fc->fc_fd = -1; + return 0; +} + +int +filecapture_init(struct filecapture *fc, size_t size) +{ + if (!fc) { + errno = EINVAL; + return -1; + } + + if (filecapture_alloc(fc, size)) + return -1; + + return 0; +} + +static void +filecapture_reset(struct filecapture *fc) +{ + fc->fc_leng = 0; + fc->fc_level = 0; + fc->fc_nlines = 0; + fc->fc_linestart = 0; + + if (fc->fc_tmpfd >= 0) { + close(fc->fc_tmpfd); + fc->fc_tmpfd = -1; + } + if (fc->fc_fd >= 0) { + close(fc->fc_fd); + fc->fc_fd = -1; + } +} + +void +filecapture_free(struct filecapture *fc) +{ + filecapture_reset(fc); + free(fc->fc_base); + fc->fc_base = NULL; + fc->fc_size = 0; + + fc->fc_linemon = NULL; + fc->fc_monarg = NULL; +} + +static int +full_write(int fd, char *buf, size_t size) +{ + while (size) { + ssize_t n = write(fd, buf, size); + if (n == -1) + return -1; + if (n == 0) { + errno = ENOSPC; + return -1; + } + buf += n; + size -= n; + } + return 0; +} + +static int +filecapture_flush(struct filecapture *fc) +{ + int res; + + if (fc->fc_level == 0) + return 0; + if (fc->fc_tmpfd == -1) { + int fd; + char tmpl[] = "/tmp/rcXXXXXX"; + fd = mkstemp(tmpl); + if (fd == -1) + return -1; + unlink(tmpl); + fc->fc_tmpfd = fd; + } + res = full_write(fc->fc_tmpfd, fc->fc_base, fc->fc_level); + if (res) + return -1; + fc->fc_level = 0; + fc->fc_linestart = 0; + return 0; +} + +static int +filecapture_get(struct filecapture *fc, int *feof) +{ + char c; + int rc; + + if (fc->fc_level == fc->fc_size) { + if (filecapture_flush(fc)) + return -1; + } + + rc = read(fc->fc_fd, &c, 1); + if (rc == -1) + return -1; + if (rc == 0) { + *feof = 1; + return 0; + } else + *feof = 0; + fc->fc_base[fc->fc_level] = c; + fc->fc_level++; + fc->fc_leng++; + if (c == '\n') { + if (fc->fc_linemon) + fc->fc_linemon(fc->fc_base + fc->fc_linestart, + fc->fc_level - fc->fc_linestart, + fc->fc_monarg); + fc->fc_linestart = fc->fc_level; + fc->fc_nlines++; + } + return 0; +} + +static int +filecapture_put(struct filecapture *fc, int *feof) +{ + if (fc->fc_level < fc->fc_size) { + int n = write(fc->fc_fd, &fc->fc_base[fc->fc_level], 1); + if (n == -1) + return -1; + if (n == 0) { + errno = ENOSPC; + return -1; + } + fc->fc_level++; + } + *feof = fc->fc_level == fc->fc_size; + return 0; +} + +void +runcap_free(struct runcap *rc) +{ + filecapture_free(&rc->rc_cap[RUNCAP_STDIN]); + filecapture_free(&rc->rc_cap[RUNCAP_STDOUT]); + filecapture_free(&rc->rc_cap[RUNCAP_STDERR]); +} + +static inline int +timeval_after(struct timeval const *a, struct timeval const *b) +{ + if (a->tv_sec == b->tv_sec) + return a->tv_usec < b->tv_usec; + else + return a->tv_sec < b->tv_usec; +} + +static inline struct timeval +timeval_diff(struct timeval const *a, struct timeval const *b) +{ + struct timeval res; + + res.tv_sec = a->tv_sec - b->tv_sec; + res.tv_usec = a->tv_usec - b->tv_usec; + if (res.tv_usec < 0) { + --res.tv_sec; + res.tv_usec += 1000000; + } + + return res; +} + +static int +runcap_start(struct runcap *rc) +{ + int p[RUNCAP_NBUF][2] = { { -1, -1}, { -1, -1 }, { -1, -1 } }; + int i; + + for (i = 0; i < RUNCAP_NBUF; i++) + if (rc->rc_cap[i].fc_size) { + if (pipe(p[i])) { + goto err; + } + } + + switch (rc->rc_pid = fork()) { + case 0: /* Child */ + if (p[RUNCAP_STDIN][0] >= 0) { + dup2(p[RUNCAP_STDIN][0], RUNCAP_STDIN); + close(p[RUNCAP_STDIN][1]); + } else if (rc->rc_cap[RUNCAP_STDIN].fc_fd >= 0) { + dup2(rc->rc_cap[RUNCAP_STDIN].fc_fd, RUNCAP_STDIN); + } + + if (p[RUNCAP_STDOUT][0] >= 0) { + dup2(p[RUNCAP_STDOUT][1], RUNCAP_STDOUT); + close(p[RUNCAP_STDOUT][0]); + } + + if (p[RUNCAP_STDERR][0] >= 0) { + dup2(p[RUNCAP_STDERR][1], RUNCAP_STDERR); + close(p[RUNCAP_STDERR][0]); + } + + i = open("/dev/null", O_RDONLY); + if (i == -1) + i = sysconf(_SC_OPEN_MAX) - 1; + while (i > RUNCAP_STDERR) { + close(i); + i--; + } + + execvp(rc->rc_program ? rc->rc_program : rc->rc_argv[0], + rc->rc_argv); + _exit(127); + + /* Parent branches */ + case -1: + break; + + default: + if (p[RUNCAP_STDIN][0] >= 0) { + close(p[RUNCAP_STDIN][0]); + rc->rc_cap[RUNCAP_STDIN].fc_fd = p[RUNCAP_STDIN][1]; + } + if (p[RUNCAP_STDOUT][0] >= 0) { + close(p[RUNCAP_STDOUT][1]); + rc->rc_cap[RUNCAP_STDOUT].fc_fd = p[RUNCAP_STDOUT][0]; + } + if (p[RUNCAP_STDERR][0] >= 0) { + close(p[RUNCAP_STDERR][1]); + rc->rc_cap[RUNCAP_STDERR].fc_fd = p[RUNCAP_STDERR][0]; + } + return 0; + } + err: + rc->rc_errno = errno; + for (i = 0; i < RUNCAP_NBUF; i++) { + close(p[i][0]); + close(p[i][1]); + } + return -1; +} + +static void +runcap_loop(struct runcap *rc) +{ + int nfd; + fd_set rds, wrs; + struct timeval finish, tv, *tvp; + + gettimeofday(&finish, NULL); + finish.tv_sec += rc->rc_timeout; + + while (1) { + int nready; + int eof; + + nfd = -1; + FD_ZERO(&rds); + FD_ZERO(&wrs); + + if (rc->rc_cap[RUNCAP_STDIN].fc_size + && rc->rc_cap[RUNCAP_STDIN].fc_fd >= 0) { + nfd = rc->rc_cap[RUNCAP_STDIN].fc_fd; + FD_SET(rc->rc_cap[RUNCAP_STDIN].fc_fd, &wrs); + } + if (rc->rc_cap[RUNCAP_STDOUT].fc_fd >= 0) { + if (rc->rc_cap[RUNCAP_STDOUT].fc_fd > nfd) + nfd = rc->rc_cap[RUNCAP_STDOUT].fc_fd; + FD_SET(rc->rc_cap[RUNCAP_STDOUT].fc_fd, &rds); + } + if (rc->rc_cap[RUNCAP_STDERR].fc_fd >= 0) { + if (rc->rc_cap[RUNCAP_STDERR].fc_fd > nfd) + nfd = rc->rc_cap[RUNCAP_STDERR].fc_fd; + FD_SET(rc->rc_cap[RUNCAP_STDERR].fc_fd, &rds); + } + nfd++; + + if (rc->rc_pid != (pid_t) -1) { + int flags = WNOHANG; + pid_t pid; + + if (nfd == 0 && rc->rc_timeout == 0) + flags = 0; + pid = waitpid(rc->rc_pid, &rc->rc_status, flags); + if (pid == -1) { + rc->rc_errno = errno; + kill(rc->rc_pid, SIGKILL); + break; + } + if (pid == rc->rc_pid) + rc->rc_pid = (pid_t) -1; + } + + if (nfd == 0 && rc->rc_pid == (pid_t) -1) + break; + + if (rc->rc_timeout) { + struct timeval now; + gettimeofday(&now, NULL); + tv = timeval_diff(&finish, &now); + if (!timeval_after(&now, &finish)) { + kill(rc->rc_pid, SIGKILL); + continue; + } + tvp = &tv; + } else { + tvp = NULL; + } + + nready = select(nfd, &rds, &wrs, NULL, tvp); + if (nready == 0) { + if (rc->rc_status) + break; + continue; + } + if (nready == -1) { + if (errno == EINTR || errno == EAGAIN) + /* retry */; + else { + rc->rc_errno = errno; + break; + } + continue; + } + + if (rc->rc_cap[RUNCAP_STDIN].fc_fd >= 0 + && FD_ISSET(rc->rc_cap[RUNCAP_STDIN].fc_fd, &wrs)) { + if (filecapture_put(&rc->rc_cap[RUNCAP_STDIN], &eof)) { + rc->rc_errno = errno; + break; + } + if (eof) { + /* FIXME: */ + close(rc->rc_cap[RUNCAP_STDIN].fc_fd); + rc->rc_cap[RUNCAP_STDIN].fc_fd = -1; + } + } + + if (rc->rc_cap[RUNCAP_STDOUT].fc_fd >= 0 + && FD_ISSET(rc->rc_cap[RUNCAP_STDOUT].fc_fd, &rds)) { + if (filecapture_get(&rc->rc_cap[RUNCAP_STDOUT], &eof)) { + rc->rc_errno = errno; + break; + } + if (eof) { + close(rc->rc_cap[RUNCAP_STDOUT].fc_fd); + rc->rc_cap[RUNCAP_STDOUT].fc_fd = -1; + } + } + if (rc->rc_cap[RUNCAP_STDERR].fc_fd >= 0 + && FD_ISSET(rc->rc_cap[RUNCAP_STDERR].fc_fd, &rds)) { + if (filecapture_get(&rc->rc_cap[RUNCAP_STDERR], &eof)) { + rc->rc_errno = errno; + break; + } + if (eof) { + close(rc->rc_cap[RUNCAP_STDERR].fc_fd); + rc->rc_cap[RUNCAP_STDERR].fc_fd = -1; + } + } + } + + if (rc->rc_pid != (pid_t)-1) { + kill(rc->rc_pid, SIGKILL); + waitpid(rc->rc_pid, &rc->rc_status, 0); + rc->rc_pid = (pid_t) -1; + } +} + +static int +runcap_init(struct runcap *rc, int flags) +{ + int res; + + if (!(flags & RCF_PROGRAM)) + rc->rc_program = NULL; + if (!(flags & RCF_TIMEOUT)) + rc->rc_timeout = 0; + if (flags & RCF_STDIN) { + if (rc->rc_cap[RUNCAP_STDIN].fc_size > 0 + && rc->rc_cap[RUNCAP_STDIN].fc_fd != -1) { + errno = EINVAL; + return -1; + } + rc->rc_cap[RUNCAP_STDIN].fc_level = 0; + } else if (filecapture_init(&rc->rc_cap[RUNCAP_STDIN], 0)) + return -1; + + if (flags & RCF_STDOUT_SIZE) + res = filecapture_alloc(&rc->rc_cap[RUNCAP_STDOUT], + rc->rc_cap[RUNCAP_STDOUT].fc_size); + else + res = filecapture_init(&rc->rc_cap[RUNCAP_STDOUT], FC_BUFSIZE); + if (res) + return res; + + if (!(flags & RCF_STDOUT_LINEMON)) { + rc->rc_cap[RUNCAP_STDOUT].fc_linemon = NULL; + rc->rc_cap[RUNCAP_STDOUT].fc_monarg = NULL; + } + + if (flags & RCF_STDERR_SIZE) + res = filecapture_alloc(&rc->rc_cap[RUNCAP_STDERR], + rc->rc_cap[RUNCAP_STDERR].fc_size); + else + res = filecapture_init(&rc->rc_cap[RUNCAP_STDERR], FC_BUFSIZE); + if (res) + return res; + + if (!(flags & RCF_STDERR_LINEMON)) { + rc->rc_cap[RUNCAP_STDERR].fc_linemon = NULL; + rc->rc_cap[RUNCAP_STDERR].fc_monarg = NULL; + } + + rc->rc_pid = (pid_t) -1; + rc->rc_status = 0; + rc->rc_errno = 0; + + return 0; +} + +static void +set_signals(void (*handler)(int signo), int sigc, int *sigv, + struct sigaction *oldact) +{ + int i; + struct sigaction act; + + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + for (i = 0; i < sigc; i++) + sigaddset(&act.sa_mask, i); + + for (i = 0; i < sigc; i++) { + act.sa_handler = handler; + sigaction(sigv[i], &act, &oldact[i]); + } +} + +static void +restore_signals(int sigc, int *sigv, struct sigaction *oldact) +{ + int i; + for (i = 0; i < sigc; i++) + sigaction(sigv[i], &oldact[i], NULL); +} + +static void +sighan(int signo) +{ + /* nothing */ +} + +int +runcap(struct runcap *rc, int flags) +{ + int sig[] = { SIGCHLD, SIGPIPE }; +#define NUMSIGNALS (sizeof(sig)/sizeof(sig[0])) + struct sigaction oldact[NUMSIGNALS]; + + if (runcap_init(rc, flags)) { + rc->rc_errno = errno; + return -1; + } + + set_signals(sighan, NUMSIGNALS, sig, oldact); + if (runcap_start(rc) == 0) + runcap_loop(rc); + restore_signals(NUMSIGNALS, sig, oldact); + if (rc->rc_errno == 0) { + if (rc->rc_cap[RUNCAP_STDOUT].fc_tmpfd != -1) { + filecapture_flush(&rc->rc_cap[RUNCAP_STDOUT]); + lseek(rc->rc_cap[RUNCAP_STDOUT].fc_tmpfd, 0, SEEK_SET); + } + if (rc->rc_cap[RUNCAP_STDERR].fc_tmpfd != -1) { + filecapture_flush(&rc->rc_cap[RUNCAP_STDERR]); + lseek(rc->rc_cap[RUNCAP_STDERR].fc_tmpfd, 0, SEEK_SET); + } + } + return rc->rc_errno == 0 ? 0 : -1; +} + + diff --git a/runcap.h b/runcap.h new file mode 100644 index 0000000..b677f05 --- /dev/null +++ b/runcap.h @@ -0,0 +1,69 @@ +/* runcap - run program and capture its output + Copyright (C) 2017 Sergey Poznyakoff + + Runcap 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. + + Runcap 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 Runcap. If not, see . */ + +#ifndef _RUNCAP_H_ +# define _RUNCAP_H_ 1 + +struct filecapture +{ + size_t fc_size; + size_t fc_leng; + size_t fc_nlines; + size_t fc_linestart; + char * fc_base; + size_t fc_level; + void (*fc_linemon) (const char *, size_t, void *); + void *fc_monarg; + int fc_tmpfd; + int fc_fd; +}; + +#define FC_BUFSIZE 4096 + +enum { + RUNCAP_STDIN, + RUNCAP_STDOUT, + RUNCAP_STDERR, + RUNCAP_NBUF +}; + +struct runcap +{ + char *rc_program; /* [IN] (Path)name of the program to run */ + char **rc_argv; /* [IN] Argument vector */ + unsigned rc_timeout; /* [IN] Execution timeout */ + struct filecapture rc_cap[RUNCAP_NBUF]; + /* rc_cap[RUNCAP_STDIN] - [IN], rest - [OUT] */ + pid_t rc_pid; /* [OUT] - PID of the process */ + int rc_status; /* [OUT] - Termination status */ + int rc_errno; /* [OUT] - Value of errno, if terminated on error */ +}; + +#define RCF_PROGRAM 0x0001 /* rc_program is set */ +#define RCF_TIMEOUT 0x0002 /* rc_timeout is set */ +#define RCF_STDIN 0x0004 /* rc_cap[RUNCAP_STDIN] is set */ +#define RCF_STDOUT_SIZE 0x0008 +#define RCF_STDOUT_LINEMON 0x0010 +#define RCF_STDERR_SIZE 0x0020 +#define RCF_STDERR_LINEMON 0x0040 + +int filecapture_init(struct filecapture *fc, size_t size); +void filecapture_free(struct filecapture *fc); + +int runcap(struct runcap *rc, int flags); +void runcap_free(struct runcap *rc); + +#endif -- cgit v1.2.1