aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2017-07-17 15:19:43 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2017-07-17 18:25:30 +0300
commitdb60fd0122ec26a7ff354c395590abf919274181 (patch)
treee33dc8805e3fcf3d5182fd1521af50b1a1fbe42b
downloadruncap-db60fd0122ec26a7ff354c395590abf919274181.tar.gz
runcap-db60fd0122ec26a7ff354c395590abf919274181.tar.bz2
Initial commit
-rw-r--r--Makefile57
-rw-r--r--runcap.c527
-rw-r--r--runcap.h69
3 files changed, 653 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+
+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 <http://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+
+#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 <http://www.gnu.org/licenses/>. */
+
+#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

Return to:

Send suggestions and report system problems to the System administrator.