X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=bacula%2Fsrc%2Flib%2Fbpipe.c;h=95e9f471fa73151a5261c29c5f5534a408aa8f2a;hb=10cfd798ced2d27f61ead2de6fe9b1bcc8e3468d;hp=46f66886049ace7e77f8184ec9f5c1d04f9944f9;hpb=2623d16dc34cd3c3fab846c498a4d55e7a3e6110;p=bacula%2Fbacula diff --git a/bacula/src/lib/bpipe.c b/bacula/src/lib/bpipe.c index 46f6688604..95e9f471fa 100644 --- a/bacula/src/lib/bpipe.c +++ b/bacula/src/lib/bpipe.c @@ -1,105 +1,217 @@ /* - * bpipe.c bi-directional pipe - * - * Kern Sibbald, November MMII - * - * Version $Id$ - */ + Bacula(R) - The Network Backup Solution -/* - Copyright (C) 2000, 2001, 2002 Kern Sibbald and John Walker + Copyright (C) 2000-2017 Kern Sibbald - 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 2 of - the License, or (at your option) any later version. + The original author of Bacula is Kern Sibbald, with contributions + from many others, a complete list can be found in the file AUTHORS. - 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 may use this file and others of this release according to the + license defined in the LICENSE file, which includes the Affero General + Public License, v3.0 ("AGPLv3") and some additional permissions and + terms pursuant to its AGPLv3 Section 7. - You should have received a copy of the GNU General Public - License along with this program; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA. + This notice must be preserved when any source code is + conveyed and/or propagated. + Bacula(R) is a registered trademark of Kern Sibbald. +*/ +/* + * bpipe.c bi-directional pipe + * + * Kern Sibbald, November MMII + * */ + #include "bacula.h" #include "jcr.h" +#ifdef HAVE_GETRLIMIT +#include +#else +/* If not available, use a wrapper that will not use it */ +#define getrlimit(a,b) -1 +#endif + +int execvp_errors[] = { + EACCES, + ENOEXEC, + EFAULT, + EINTR, + E2BIG, + ENAMETOOLONG, + ENOMEM, +#ifndef HAVE_WIN32 + ETXTBSY, +#endif + ENOENT +}; +int num_execvp_errors = (int)(sizeof(execvp_errors)/sizeof(int)); #define MAX_ARGV 100 -static void build_argc_argv(char *cmd, int *bargc, char *bargv[], int max_arg); +#if !defined(HAVE_WIN32) +static void build_argc_argv(char *cmd, int *bargc, char *bargv[], int max_arg); +void build_sh_argc_argv(char *cmd, int *bargc, char *bargv[], int max_arg) +{ + bargv[0] = (char *)"/bin/sh"; + bargv[1] = (char *)"-c"; + bargv[2] = cmd; + bargv[3] = NULL; + *bargc = 3; +} /* * Run an external program. Optionally wait a specified number * of seconds. Program killed if wait exceeded. We open * a bi-directional pipe so that the user can read from and - * write to the program. + * write to the program. */ -BPIPE *open_bpipe(char *prog, int wait, char *mode) +BPIPE *open_bpipe(char *prog, int wait, const char *mode, char *envp[]) { char *bargv[MAX_ARGV]; - int bargc; + int bargc, i; int readp[2], writep[2]; POOLMEM *tprog; - int mode_read, mode_write; + int mode_read, mode_write, mode_shell; BPIPE *bpipe; + int save_errno; +#if !defined(HAVE_FCNTL_F_CLOSEM) && !defined(HAVE_CLOSEFROM) + struct rlimit rl; + int64_t rlimitResult=0; +#endif + if (!prog || !*prog) { + /* execve(3) A component of the file does not name an existing file or file is an empty string. */ + errno = ENOENT; + return NULL; + } + bpipe = (BPIPE *)malloc(sizeof(BPIPE)); memset(bpipe, 0, sizeof(BPIPE)); mode_read = (mode[0] == 'r'); mode_write = (mode[0] == 'w' || mode[1] == 'w'); + /* mode is at least 2 bytes long, can be 3, rs, rws, ws */ + mode_shell = (mode[1] == 's' || (mode[1] && mode[2] == 's')); /* Build arguments for running program. */ tprog = get_pool_memory(PM_FNAME); - pm_strcpy(&tprog, prog); - build_argc_argv(mp_chr(tprog), &bargc, bargv, MAX_ARGV); -#ifdef xxxxxx + pm_strcpy(tprog, prog); + if (mode_shell) { + build_sh_argc_argv(tprog, &bargc, bargv, MAX_ARGV); + + } else { + build_argc_argv(tprog, &bargc, bargv, MAX_ARGV); + } + + /* Unable to parse the command, avoid segfault after the fork() */ + if (bargc == 0 || bargv[0] == NULL) { + free_pool_memory(tprog); + free(bpipe); + /* execve(3) A component of the file does not name an existing file or file is an empty string. */ + errno = ENOENT; + return NULL; + } + +#ifdef xxxxxx printf("argc=%d\n", bargc); - int i; for (i=0; iworker_pid = fork()) { - case -1: /* error */ + case -1: /* error */ + save_errno = errno; + if (mode_write) { + close(writep[0]); + close(writep[1]); + } + if (mode_read) { + close(readp[0]); + close(readp[1]); + } free(bpipe); + free_pool_memory(tprog); + errno = save_errno; return NULL; - case 0: /* child */ + case 0: /* child */ if (mode_write) { - close(writep[1]); - dup2(writep[0], 0); /* Dup our write to his stdin */ + close(writep[1]); + dup2(writep[0], 0); /* Dup our write to his stdin */ } if (mode_read) { - close(readp[0]); /* Close unused child fds */ - dup2(readp[1], 1); /* dup our read to his stdout */ - dup2(readp[1], 2); /* and his stderr */ + close(readp[0]); /* Close unused child fds */ + dup2(readp[1], 1); /* dup our read to his stdout */ + dup2(readp[1], 2); /* and his stderr */ } - execvp(bargv[0], bargv); /* call the program */ - exit(errno); /* shouldn't get here */ - default: /* parent */ +#if HAVE_FCNTL_F_CLOSEM + fcntl(3, F_CLOSEM); +#elif HAVE_CLOSEFROM + closefrom(3); +#else + for (i=rlimitResult; i >= 3; i--) { + close(i); + } +#endif + + /* Setup the environment if requested, we do not use execvpe() + * because it's not wildly available + * TODO: Implement envp to windows version of bpipe + */ + setup_env(envp); + + execvp(bargv[0], bargv); /* call the program */ + /* Convert errno into an exit code for later analysis */ + for (i=0; i< num_execvp_errors; i++) { + if (execvp_errors[i] == errno) { + _exit(200 + i); /* exit code => errno */ + } + } + /* Do not flush stdio */ + _exit(255); /* unknown errno */ + + default: /* parent */ break; } + free_pool_memory(tprog); if (mode_read) { - close(readp[1]); /* close unused parent fds */ + close(readp[1]); /* close unused parent fds */ bpipe->rfd = fdopen(readp[0], "r"); /* open file descriptor */ } if (mode_write) { @@ -109,7 +221,7 @@ BPIPE *open_bpipe(char *prog, int wait, char *mode) bpipe->worker_stime = time(NULL); bpipe->wait = wait; if (wait > 0) { - bpipe->timer_id = start_child_timer(bpipe->worker_pid, wait); + bpipe->timer_id = start_child_timer(NULL, bpipe->worker_pid, wait); } return bpipe; } @@ -122,18 +234,23 @@ int close_wpipe(BPIPE *bpipe) if (bpipe->wfd) { fflush(bpipe->wfd); if (fclose(bpipe->wfd) != 0) { - stat = 0; + stat = 0; } bpipe->wfd = NULL; } return stat; } -/* Close both pipes and free resources */ -int close_bpipe(BPIPE *bpipe) +/* + * Close both pipes and free resources + * + * Returns: 0 on success + * berrno on failure + */ +int close_bpipe(BPIPE *bpipe) { int chldstatus = 0; - int stat = 0; + int stat = 0; int wait_option; int remaining_wait; pid_t wpid = 0; @@ -150,7 +267,7 @@ int close_bpipe(BPIPE *bpipe) } if (bpipe->wait == 0) { - wait_option = 0; /* wait indefinitely */ + wait_option = 0; /* wait indefinitely */ } else { wait_option = WNOHANG; /* don't hang */ } @@ -158,116 +275,241 @@ int close_bpipe(BPIPE *bpipe) /* wait for worker child to exit */ for ( ;; ) { - wpid = waitpid(bpipe->worker_pid, &chldstatus, wait_option); - if (wpid == bpipe->worker_pid || (wpid == -1 && errno != EINTR)) { - break; + Dmsg2(100, "Wait for %d opt=%d\n", bpipe->worker_pid, wait_option); + do { + wpid = waitpid(bpipe->worker_pid, &chldstatus, wait_option); + } while (wpid == -1 && (errno == EINTR || errno == EAGAIN)); + if (wpid == bpipe->worker_pid || wpid == -1) { + berrno be; + stat = errno; + Dmsg3(100, "Got break wpid=%d status=%d ERR=%s\n", wpid, chldstatus, + wpid==-1?be.bstrerror():"none"); + break; } + Dmsg3(100, "Got wpid=%d status=%d ERR=%s\n", wpid, chldstatus, + wpid==-1?strerror(errno):"none"); if (remaining_wait > 0) { - bmicrosleep(1, 0); /* wait one second */ - remaining_wait--; + bmicrosleep(1, 0); /* wait one second */ + remaining_wait--; } else { - stat = 1; /* set error status */ - errno = ETIME; /* set timed out */ - wpid = -1; + stat = ETIME; /* set error status */ + wpid = -1; break; /* don't wait any longer */ } } if (wpid > 0) { - if (WIFEXITED(chldstatus)) { /* process exit()ed */ - stat = WEXITSTATUS(chldstatus); + if (WIFEXITED(chldstatus)) { /* process exit()ed */ + stat = WEXITSTATUS(chldstatus); + if (stat != 0) { + Dmsg1(100, "Non-zero status %d returned from child.\n", stat); + stat |= b_errno_exit; /* exit status returned */ + } + Dmsg1(100, "child status=%d\n", stat & ~b_errno_exit); } else if (WIFSIGNALED(chldstatus)) { /* process died */ - stat = 1; - } - if (stat != 0) { - errno = ECHILD; /* set child errno */ +#ifndef HAVE_WIN32 + stat = WTERMSIG(chldstatus); +#else + stat = 1; /* fake child status */ +#endif + Dmsg1(100, "Child died from signal %d\n", stat); + stat |= b_errno_signal; /* exit signal returned */ } - } + } if (bpipe->timer_id) { stop_child_timer(bpipe->timer_id); } free(bpipe); -#ifdef HAVE_FREEBSD_OS - stat = 0; /* kludge because FreeBSD doesn't seem to return valid status */ -#endif + Dmsg2(100, "returning stat=%d,%d\n", stat & ~(b_errno_exit|b_errno_signal), stat); return stat; } +/* + * Build argc and argv from a string + */ +static void build_argc_argv(char *cmd, int *bargc, char *bargv[], int max_argv) +{ + int i; + char *p, *q, quote; + int argc = 0; + + argc = 0; + for (i=0; iSUCCESS), so we check if the watchdog killed the program. + * + * Contrary to my normal calling conventions, this program * * Returns: 0 on success - * non-zero on error + * non-zero on error == berrno status */ -int run_program(char *prog, int wait, POOLMEM *results) +int run_program(char *prog, int wait, POOLMEM *&results) { BPIPE *bpipe; int stat1, stat2; char *mode; - mode = (char *)(results != NULL ? "r" : ""); + mode = (char *)"r"; bpipe = open_bpipe(prog, wait, mode); if (!bpipe) { - return 0; + return ENOENT; } - if (results) { - mp_chr(results)[0] = 0; - stat1 = fgets(mp_chr(results), sizeof_pool_memory(results), bpipe->rfd) == NULL; - } else { + results[0] = 0; + int len = sizeof_pool_memory(results) - 1; + fgets(results, len, bpipe->rfd); + results[len] = 0; + if (feof(bpipe->rfd)) { stat1 = 0; + } else { + stat1 = ferror(bpipe->rfd); + } + if (stat1 < 0) { + berrno be; + Dmsg2(100, "Run program fgets stat=%d ERR=%s\n", stat1, be.bstrerror(errno)); + } else if (stat1 != 0) { + Dmsg1(100, "Run program fgets stat=%d\n", stat1); + if (bpipe->timer_id) { + Dmsg1(100, "Run program fgets killed=%d\n", bpipe->timer_id->killed); + /* NB: I'm not sure it is really useful for run_program. Without the + * following lines run_program would not detect if the program was killed + * by the watchdog. */ + if (bpipe->timer_id->killed) { + stat1 = ETIME; + pm_strcpy(results, _("Program killed by Bacula (timeout)\n")); + } + } } stat2 = close_bpipe(bpipe); - return stat2 != 0 ? stat2 : stat1; + stat1 = stat2 != 0 ? stat2 : stat1; + Dmsg1(100, "Run program returning %d\n", stat1); + return stat1; } - /* - * Build argc and argv from a string + * Run an external program. Optionally wait a specified number + * of seconds. Program killed if wait exceeded (it is done by the + * watchdog, as fgets is a blocking function). + * + * If the watchdog kills the program, fgets returns, and ferror is set + * to 1 (=>SUCCESS), so we check if the watchdog killed the program. + * + * Return the full output from the program (not only the first line). + * + * Contrary to my normal calling conventions, this program + * + * Returns: 0 on success + * non-zero on error == berrno status + * */ -static void build_argc_argv(char *cmd, int *bargc, char *bargv[], int max_argv) +int run_program_full_output(char *prog, int wait, POOLMEM *&results, char *env[]) { - int i; - char *p, *q, quote; - int argc = 0; + BPIPE *bpipe; + int stat1, stat2; + char *mode; + POOLMEM* tmp; + char *buf; + const int bufsize = 32000; - argc = 0; - for (i=0; irfd); + buf[bufsize] = 0; + pm_strcat(tmp, buf); + if (feof(bpipe->rfd)) { + stat1 = 0; + Dmsg1(100, "Run program fgets stat=%d\n", stat1); + break; + } else { + stat1 = ferror(bpipe->rfd); + } + if (stat1 < 0) { + berrno be; + Dmsg2(100, "Run program fgets stat=%d ERR=%s\n", stat1, be.bstrerror()); + break; + } else if (stat1 != 0) { + Dmsg1(200, "Run program fgets stat=%d\n", stat1); + if (bpipe->timer_id && bpipe->timer_id->killed) { + Dmsg1(100, "Run program saw fgets killed=%d\n", bpipe->timer_id->killed); + break; + } } } - *bargc = argc; + /* + * We always check whether the timer killed the program. We would see + * an eof even when it does so we just have to trust the killed flag + * and set the timer values to avoid edge cases where the program ends + * just as the timer kills it. + */ + if (bpipe->timer_id && bpipe->timer_id->killed) { + Dmsg1(100, "Run program fgets killed=%d\n", bpipe->timer_id->killed); + pm_strcpy(tmp, _("Program killed by Bacula (timeout)\n")); + stat1 = ETIME; + } + pm_strcpy(results, tmp); + Dmsg3(200, "resadr=0x%x reslen=%d res=%s\n", results, strlen(results), results); + stat2 = close_bpipe(bpipe); + stat1 = stat2 != 0 ? stat2 : stat1; + + Dmsg1(100, "Run program returning %d\n", stat1); +bail_out: + free_pool_memory(tmp); + free(buf); + return stat1; }