1/* $OpenBSD: cmd_exec.c,v 1.13 2023/09/04 11:35:11 espie Exp $ */ 2/* 3 * Copyright (c) 2001 Marc Espie. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS 15 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD 18 * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/types.h> 28#include <sys/wait.h> 29#include <errno.h> 30#include <stdio.h> 31#include <string.h> 32#include <unistd.h> 33#include "defines.h" 34#include "cmd_exec.h" 35#include "buf.h" 36#include "memory.h" 37#include "pathnames.h" 38#include "job.h" 39#include "str.h" 40 41/* The following array is used to make a fast determination of which 42 * characters are interpreted specially by the shell. If a command 43 * contains any of these characters, it is executed by the shell, not 44 * directly by us. */ 45static char meta[256]; 46 47void 48CmdExec_Init(void) 49{ 50 char *p; 51 52 for (p = "#=|^(){};&<>*?[]:$`\\\n~"; *p != '\0'; p++) 53 meta[(unsigned char) *p] = 1; 54 /* The null character serves as a sentinel in the string. */ 55 meta[0] = 1; 56} 57 58static char ** 59recheck_command_for_shell(char **av) 60{ 61 char *runsh[] = { 62 "!", "alias", "cd", "eval", "exit", "read", "set", "ulimit", 63 "unalias", "unset", "wait", "umask", NULL 64 }; 65 66 char **p; 67 68 /* optimization: if exec cmd, we avoid the intermediate shell */ 69 if (strcmp(av[0], "exec") == 0) 70 av++; 71 72 if (!av[0]) 73 return NULL; 74 75 for (p = runsh; *p; p++) 76 if (strcmp(av[0], *p) == 0) 77 return NULL; 78 79 return av; 80} 81 82void 83run_command(const char *cmd, bool errCheck) 84{ 85 const char *p; 86 char *shargv[4]; 87 char **todo; 88 89 shargv[0] = _PATH_BSHELL; 90 91 shargv[1] = errCheck ? "-ec" : "-c"; 92 shargv[2] = (char *)cmd; 93 shargv[3] = NULL; 94 95 todo = shargv; 96 97 98 /* Search for meta characters in the command. If there are no meta 99 * characters, there's no need to execute a shell to execute the 100 * command. */ 101 for (p = cmd; !meta[(unsigned char)*p]; p++) 102 continue; 103 if (*p == '\0') { 104 char *bp; 105 char **av; 106 int argc; 107 /* No meta-characters, so probably no need to exec a shell. 108 * Break the command into words to form an argument vector 109 * we can execute. */ 110 av = brk_string(cmd, &argc, &bp); 111 av = recheck_command_for_shell(av); 112 if (av != NULL) 113 todo = av; 114 } 115 execvp(todo[0], todo); 116 if (errno == ENOENT) 117 fprintf(stderr, "%s: not found\n", todo[0]); 118 else 119 perror(todo[0]); 120 _exit(1); 121} 122 123char * 124Cmd_Exec(const char *cmd, char **err) 125{ 126 int fds[2]; /* Pipe streams */ 127 pid_t cpid; /* Child PID */ 128 char *result; /* Result */ 129 int status; /* Command exit status */ 130 BUFFER buf; /* Buffer to store the result. */ 131 char *cp; /* Pointer into result. */ 132 ssize_t cc; /* Characters read from pipe. */ 133 size_t length; /* Total length of result. */ 134 135 136 *err = NULL; 137 138 /* Open a pipe for retrieving shell's output. */ 139 if (pipe(fds) == -1) { 140 *err = "Couldn't create pipe for \"%s\""; 141 goto bad; 142 } 143 144 /* Fork */ 145 switch (cpid = fork()) { 146 case 0: 147 reset_signal_mask(); 148 /* Close input side of pipe */ 149 (void)close(fds[0]); 150 151 /* Duplicate the output stream to the shell's output, then 152 * shut the extra thing down. Note we don't fetch the error 153 * stream: user can use redirection to grab it as this goes 154 * through /bin/sh. 155 */ 156 if (fds[1] != 1) { 157 (void)dup2(fds[1], 1); 158 (void)close(fds[1]); 159 } 160 161 run_command(cmd, false); 162 /*NOTREACHED*/ 163 164 case -1: 165 *err = "Couldn't exec \"%s\""; 166 goto bad; 167 168 default: 169 /* No need for the writing half. */ 170 (void)close(fds[1]); 171 172 Buf_Init(&buf, MAKE_BSIZE); 173 174 do { 175 char grab[BUFSIZ]; 176 177 cc = read(fds[0], grab, sizeof(grab)); 178 if (cc > 0) 179 Buf_AddChars(&buf, cc, grab); 180 } while (cc > 0 || (cc == -1 && errno == EINTR)); 181 182 /* Close the input side of the pipe. */ 183 (void)close(fds[0]); 184 185 /* Wait for the child to exit. */ 186 while (waitpid(cpid, &status, 0) == -1 && errno == EINTR) 187 continue; 188 189 if (cc == -1) 190 *err = "Couldn't read shell's output for \"%s\""; 191 192 if (status) 193 *err = "\"%s\" returned non-zero status"; 194 195 length = Buf_Size(&buf); 196 result = Buf_Retrieve(&buf); 197 198 /* The result is null terminated, Convert newlines to spaces. */ 199 cp = result + length - 1; 200 201 if (cp >= result && *cp == '\n') 202 /* A final newline is just stripped. */ 203 *cp-- = '\0'; 204 205 while (cp >= result) { 206 if (*cp == '\n') 207 *cp = ' '; 208 cp--; 209 } 210 break; 211 } 212 return result; 213 bad: 214 return estrdup(""); 215} 216 217