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. */
45 static char meta[256];
46
47 void
CmdExec_Init(void)48 CmdExec_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
58 static char **
recheck_command_for_shell(char ** av)59 recheck_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
82 void
run_command(const char * cmd,bool errCheck)83 run_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
123 char *
Cmd_Exec(const char * cmd,char ** err)124 Cmd_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