diff options
-rw-r--r-- | runcap.3 | 25 | ||||
-rw-r--r-- | runcap.c | 7 | ||||
-rw-r--r-- | runcap.h | 2 | ||||
-rw-r--r-- | t/Makefile.am | 3 | ||||
-rw-r--r-- | t/env.at | 28 | ||||
-rw-r--r-- | t/rt.c | 142 | ||||
-rw-r--r-- | t/testsuite.at | 2 |
7 files changed, 188 insertions, 21 deletions
@@ -13,7 +13,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with runcap. If not, see <http://www.gnu.org/licenses/>. -.TH RUNCAP 2 "January 30, 2020" "RUNCAP" "User Commands" +.TH RUNCAP 2 "March 14, 2024" "RUNCAP" "User Commands" .SH NAME runcap \- run external process and capture its stdout and stderr .SH SYNOPSIS @@ -67,13 +67,14 @@ The \fBstruct runcap\fR is defined as follows: .nf struct runcap { - char *rc_program; /* [\fIIN\fR] (Path)name of the program to run */ - char **rc_argv; /* [\fIIN\fR] Argument vector */ - unsigned rc_timeout; /* [\fIIN\fR] Execution timeout */ + char *rc_program; /* [\fIIN\fR] (Path)name of the program to run. */ + char **rc_argv; /* [\fIIN\fR] Argument vector. */ + char **rc_env; /* [\fIIN\fR] Environment variables. */ + unsigned rc_timeout; /* [\fIIN\fR] Execution timeout. */ struct stream_capture rc_cap[3]; - pid_t rc_pid; /* [\fIOUT\fR] PID of the process */ - int rc_status; /* [\fIOUT\fR] Termination status */ - int rc_errno; /* [\fIOUT\fR] System error code */ + pid_t rc_pid; /* [\fIOUT\fR] PID of the process. */ + int rc_status; /* [\fIOUT\fR] Termination status. */ + int rc_errno; /* [\fIOUT\fR] System error code. */ }; .fi .in @@ -96,6 +97,14 @@ Time to wait for the program termination, in seconds. If initialized, the \fBRCF_TIMEOUT\fR bit must be set in \fIflags\fR. If not set, .B runcap will wait indefinitely. +.TP +.B rc_env +A +.BR NULL -terminated +array of environment variables. Each element (except the last +.BR NULL ) +has the form "\fIname\fR=\fIvalue\fR". If initialized, the +\fBRCF_ENV\fR bit must be set in \fIflags\fR. .PP The three streams associated with the running command are described by the @@ -476,7 +485,7 @@ archive(void) .SH AUTHORS Sergey Poznyakoff .SH COPYRIGHT -Copyright \(co 2017--2020 Sergey Poznyakoff +Copyright \(co 2017--2024 Sergey Poznyakoff .br .na License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> @@ -260,6 +260,8 @@ timeval_diff(struct timeval const *a, struct timeval const *b) return res; } +extern char **environ; + static int runcap_start(struct runcap *rc) { @@ -306,6 +308,8 @@ runcap_start(struct runcap *rc) i--; } + if (rc->rc_env) + environ = rc->rc_env; execvp(rc->rc_program ? rc->rc_program : rc->rc_argv[0], rc->rc_argv); _exit(127); @@ -479,7 +483,8 @@ runcap_init(struct runcap *rc, int flags) rc->rc_program = NULL; if (!(flags & RCF_TIMEOUT)) rc->rc_timeout = 0; - + if (!(flags & RCF_ENV)) + rc->rc_env = NULL; if (flags & RCF_STDIN) { if (rc->rc_cap[RUNCAP_STDIN].sc_size > 0 && rc->rc_cap[RUNCAP_STDIN].sc_fd != -1) { @@ -47,6 +47,7 @@ struct runcap { char *rc_program; /* [IN] (Path)name of the program to run */ char **rc_argv; /* [IN] Argument vector */ + char **rc_env; /* [IN] Environment variables */ unsigned rc_timeout; /* [IN] Execution timeout */ struct stream_capture rc_cap[RUNCAP_NBUF]; /* rc_cap[RUNCAP_STDIN] - [IN], rest - [OUT] */ @@ -58,6 +59,7 @@ struct runcap #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_ENV 0x0008 /* rc_env is set */ #define RCF_SC_SIZE 0x1 /* sc_size is set */ #define RCF_SC_LINEMON 0x2 /* sc_linemon is set*/ diff --git a/t/Makefile.am b/t/Makefile.am index 9e958ad..1905212 100644 --- a/t/Makefile.am +++ b/t/Makefile.am @@ -55,7 +55,8 @@ TESTSUITE_AT = \ nocap.at\ redirect.at\ seek00.at\ - seek01.at + seek01.at\ + env.at # Add more files here TESTSUITE = $(srcdir)/testsuite diff --git a/t/env.at b/t/env.at new file mode 100644 index 0000000..42883a1 --- /dev/null +++ b/t/env.at @@ -0,0 +1,28 @@ +# Testsuite for runcap - run program and capture its output -*- autotest -*- +# Copyright (C) 2024 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/>. +AT_SETUP([environment modifications]) +AT_KEYWORDS([env environ]) +AT_CHECK([rt -n stdout -e FOO=bar -- /bin/sh -c 'echo $FOO'], +[0], +[res=0 +exit code: 0 +stdout: 1 lines, 4 bytes +stderr: 0 lines, 0 bytes +stdout listing: + 1: bar +stdout listing ends +]) +AT_CLEANUP @@ -25,6 +25,7 @@ #include <sys/wait.h> #include <errno.h> #include <inttypes.h> +#include <assert.h> #include "runcap.h" static char *progname; @@ -49,6 +50,10 @@ usage(int code) fprintf(fp, "tests the runcap library\n"); fprintf(fp, "OPTIONS are:\n\n"); fprintf(fp, " -S all|stderr|stdout selects capture for the next -m, -N, or -s option\n"); + fprintf(fp, " -e VAR=NAME set environment variable\n"); + fprintf(fp, " -e -VAR unset environment variable\n"); + fprintf(fp, " -e - clear environment (except for PATH,\n"); + fprintf(fp, " HOME, and LOGNAME)\n"); fprintf(fp, " -f FILE reads stdin from FILE\n"); fprintf(fp, " -i inline read (use before -f)\n"); fprintf(fp, " -N disable capturing\n"); @@ -234,11 +239,9 @@ readreq_do(struct runcap *rc, struct readreq *req) if (req->full) { char *buf = malloc(req->count); ssize_t n; - - if (!buf) { - perror("malloc"); - abort(); - } + + assert(buf != NULL); + n = runcap_read(rc, req->what, buf, req->count); if (n < 0) { perror("runcap_read"); @@ -279,6 +282,123 @@ open_outfile(char *file, int stream, struct runcap *rc, int *flags) *flags |= RCF_SC_TO_FLAG(RCF_SC_STORFD, stream); } +static int +getenvind(char **env, char const *name) +{ + size_t i; + for (i = 0; env[i]; i++) { + char const *p; + char *q; + + for (p = name, q = env[i]; *p == *q; p++, q++) + ; + if (*p == 0 && *q == '=') { + return i; + } + } + return -1; +} + +char ** +envdup(char **env) +{ + int i; + char **new_env; + + for (i = 0; env[i]; i++) + ; + + new_env = calloc(i+1, sizeof(env[0])); + assert(new_env != NULL); + + for (i = 0; env[i]; i++) { + new_env[i] = strdup(env[i]); + assert(new_env[i] != NULL); + } + new_env[i] = NULL; + + return new_env; +} + +char **envupdate(char **, char *); + +char ** +envclear(char **env) +{ + int i, j; + static char *keep[] = { + "PATH", + "HOME", + "LOGNAME", + NULL + }; + char **new_env = calloc(1, sizeof(new_env[0])); + assert(new_env != NULL); + for (i = 0; keep[i]; i++) { + j = getenvind(env, keep[i]); + if (j != -1) + new_env = envupdate(new_env, env[j]); + } + for (i = 0; env[i]; i++) + free(env[i]); + free(env); + return new_env; +} + +char ** +envappend(char **env, char *arg) +{ + int i; + + for (i = 0; env[i]; i++) + ; + env = realloc(env, (i+2) * sizeof(env[0])); + assert(env != NULL); + env[i] = strdup(arg); + assert(env[i] != NULL); + env[i+1] = NULL; + return env; +} + +char ** +envdelete(char **env, char *arg) +{ + int i = getenvind(env, arg); + if (i != -1) { + int n; + for (n = 0; env[n]; n++) + ; + free(env[i]); + memmove(env + i, env + i + 1, (n - i) * sizeof(env[0])); + } + return env; +} + +extern char **environ; + +char ** +envupdate(char **env, char *arg) +{ + if (env == NULL) + env = envdup(environ); + if (*arg == '-') { + if (arg[1] == 0) + env = envclear(env); + else + env = envdelete(env, arg+1); + } else { + int i = getenvind(env, arg); + if (i == -1) + env = envappend(env, arg); + else { + free(env[i]); + env[i] = strdup(arg); + assert(env[i] != NULL); + } + } + return env; +} + int main(int argc, char **argv) { @@ -306,8 +426,13 @@ main(int argc, char **argv) progname++; else progname = argv[0]; - while ((c = getopt(argc, argv, "?f:iNn:mo:p:r:S:s:t:")) != EOF) { + memset(&rc, 0, sizeof(rc)); + while ((c = getopt(argc, argv, "?e:f:iNn:mo:p:r:S:s:t:")) != EOF) { switch (c) { + case 'e': + rc.rc_env = envupdate(rc.rc_env, optarg); + rcf |= RCF_ENV; + break; case 'f': fd = open(optarg, O_RDONLY); if (fd == -1) { @@ -325,10 +450,7 @@ main(int argc, char **argv) } size = st.st_size; buffer = malloc(size + 1); - if (!buffer) { - error("not enough memory"); - exit(1); - } + assert(buffer != NULL); rc.rc_cap[RUNCAP_STDIN].sc_size = size; rc.rc_cap[RUNCAP_STDIN].sc_base = buffer; diff --git a/t/testsuite.at b/t/testsuite.at index a3dd7c9..bbce1ff 100644 --- a/t/testsuite.at +++ b/t/testsuite.at @@ -32,4 +32,4 @@ m4_include([seek00.at]) m4_include([seek01.at]) m4_include([read.at]) m4_include([redirect.at]) - +m4_include([env.at]) |