Bug Summary

File:src/usr.bin/vi/build/../ex/ex_filter.c
Warning:line 157, column 9
This function call is prohibited after a successful vfork

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name ex_filter.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/vi/build/obj -resource-dir /usr/local/lib/clang/13.0.0 -I /usr/src/usr.bin/vi/build -I /usr/src/usr.bin/vi/build/../include -I . -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/vi/build/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/vi/build/../ex/ex_filter.c
1/* $OpenBSD: ex_filter.c,v 1.15 2016/08/01 18:27:35 bentley Exp $ */
2
3/*-
4 * Copyright (c) 1991, 1993, 1994
5 * The Regents of the University of California. All rights reserved.
6 * Copyright (c) 1991, 1993, 1994, 1995, 1996
7 * Keith Bostic. All rights reserved.
8 *
9 * See the LICENSE file for redistribution information.
10 */
11
12#include "config.h"
13
14#include <sys/stat.h>
15#include <sys/types.h>
16#include <sys/queue.h>
17
18#include <bitstring.h>
19#include <errno(*__errno()).h>
20#include <fcntl.h>
21#include <limits.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <unistd.h>
26
27#include "../common/common.h"
28
29static int filter_ldisplay(SCR *, FILE *);
30
31/*
32 * ex_filter --
33 * Run a range of lines through a filter utility and optionally
34 * replace the original text with the stdout/stderr output of
35 * the utility.
36 *
37 * PUBLIC: int ex_filter(SCR *,
38 * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype);
39 */
40int
41ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, char *cmd,
42 enum filtertype ftype)
43{
44 FILE *ifp, *ofp;
45 pid_t parent_writer_pid, utility_pid;
46 recno_t nread;
47 int input[2], output[2], fd, rval;
48 char *name, tname[] = "/tmp/vi.XXXXXXXXXX";
49
50 rval = 0;
51
52 /* Set return cursor position, which is never less than line 1. */
53 *rp = *fm;
54 if (rp->lno == 0)
1
Assuming field 'lno' is not equal to 0
2
Taking false branch
55 rp->lno = 1;
56
57 /* We're going to need a shell. */
58 if (opts_empty(sp, O_SHELL, 0))
3
Assuming the condition is false
4
Taking false branch
59 return (1);
60
61 /*
62 * There are three different processes running through this code.
63 * They are the utility, the parent-writer and the parent-reader.
64 * The parent-writer is the process that writes from the file to
65 * the utility, the parent reader is the process that reads from
66 * the utility.
67 *
68 * Input and output are named from the utility's point of view.
69 * The utility reads from input[0] and the parent(s) write to
70 * input[1]. The parent(s) read from output[0] and the utility
71 * writes to output[1].
72 *
73 * !!!
74 * Historically, in the FILTER_READ case, the utility reads from
75 * the terminal (e.g. :r! cat works). Otherwise open up utility
76 * input pipe.
77 */
78 ofp = NULL((void *)0);
79 input[0] = input[1] = output[0] = output[1] = -1;
80
81 if (ftype == FILTER_BANG) {
5
Assuming 'ftype' is not equal to FILTER_BANG
6
Taking false branch
82 fd = mkstemp(tname);
83 if (fd == -1) {
84 msgq(sp, M_SYSERR,
85 "Unable to create temporary file");
86 if (fd != -1) {
87 (void)close(fd);
88 (void)unlink(tname);
89 }
90 goto err;
91 }
92 if (unlink(tname) == -1)
93 msgq(sp, M_SYSERR, "unlink");
94 if ((ifp = fdopen(fd, "w")) == NULL((void *)0)) {
95 msgq(sp, M_SYSERR, "fdopen");
96 (void)close(fd);
97 goto err;
98 }
99 if ((input[0] = dup(fd)) == -1) {
100 msgq(sp, M_SYSERR, "dup");
101 (void)fclose(ifp);
102 goto err;
103 }
104 /*
105 * Write the selected lines into the temporary file.
106 * This instance of ifp is closed by ex_writefp.
107 */
108 if (ex_writefp(sp, "filter", ifp, fm, tm, NULL((void *)0), NULL((void *)0), 1))
109 goto err;
110 if (lseek(input[0], 0, SEEK_SET0) == -1) {
111 msgq(sp, M_SYSERR, "lseek");
112 goto err;
113 }
114 } else if (ftype != FILTER_READ && pipe(input) < 0) {
7
Assuming 'ftype' is equal to FILTER_READ
115 msgq(sp, M_SYSERR, "pipe");
116 goto err;
117 }
118
119 /* Open up utility output pipe. */
120 if (pipe(output) < 0) {
8
Assuming the condition is false
9
Taking false branch
121 msgq(sp, M_SYSERR, "pipe");
122 goto err;
123 }
124 if ((ofp = fdopen(output[0], "r")) == NULL((void *)0)) {
10
Assuming the condition is false
11
Taking false branch
125 msgq(sp, M_SYSERR, "fdopen");
126 goto err;
127 }
128
129 /* Fork off the utility process. */
130 switch (utility_pid = vfork()) {
12
Control jumps to 'case 0:' at line 144
131 case -1: /* Error. */
132 msgq(sp, M_SYSERR, "vfork");
133err: if (input[0] != -1)
134 (void)close(input[0]);
135 if (input[1] != -1)
136 (void)close(input[1]);
137 if (ofp != NULL((void *)0))
138 (void)fclose(ofp);
139 else if (output[0] != -1)
140 (void)close(output[0]);
141 if (output[1] != -1)
142 (void)close(output[1]);
143 return (1);
144 case 0: /* Utility. */
145 /*
146 * Redirect stdin from the read end of the input pipe, and
147 * redirect stdout/stderr to the write end of the output pipe.
148 *
149 * !!!
150 * Historically, ex only directed stdout into the input pipe,
151 * letting stderr come out on the terminal as usual. Vi did
152 * not, directing both stdout and stderr into the input pipe.
153 * We match that practice in both ex and vi for consistency.
154 */
155 if (input[0] != -1)
13
Taking false branch
156 (void)dup2(input[0], STDIN_FILENO0);
157 (void)dup2(output[1], STDOUT_FILENO1);
14
This function call is prohibited after a successful vfork
158 (void)dup2(output[1], STDERR_FILENO2);
159
160 /* Close the utility's file descriptors. */
161 if (input[0] != -1)
162 (void)close(input[0]);
163 if (input[1] != -1)
164 (void)close(input[1]);
165 (void)close(output[0]);
166 (void)close(output[1]);
167
168 if ((name = strrchr(O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, '/')) == NULL((void *)0))
169 name = O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
;
170 else
171 ++name;
172
173 execl(O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, name, "-c", cmd, (char *)NULL((void *)0));
174 msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL)((((&((sp))->opts[((O_SHELL))])->flags) & ((0x01
))) ? ((sp))->gp->opts[((sp))->opts[((O_SHELL))].o_cur
.val].o_cur.str : ((sp))->opts[((O_SHELL))].o_cur.str)
, "execl: %s");
175 _exit (127);
176 /* NOTREACHED */
177 default: /* Parent-reader, parent-writer. */
178 /* Close the pipe ends neither parent will use. */
179 if (input[0] != -1)
180 (void)close(input[0]);
181 (void)close(output[1]);
182 break;
183 }
184
185 /*
186 * FILTER_RBANG, FILTER_READ:
187 *
188 * Reading is the simple case -- we don't need a parent writer,
189 * so the parent reads the output from the read end of the output
190 * pipe until it finishes, then waits for the child. Ex_readfp
191 * appends to the MARK, and closes ofp.
192 *
193 * For FILTER_RBANG, there is nothing to write to the utility.
194 * Make sure it doesn't wait forever by closing its standard
195 * input.
196 *
197 * !!!
198 * Set the return cursor to the last line read in for FILTER_READ.
199 * Historically, this behaves differently from ":r file" command,
200 * which leaves the cursor at the first line read in. Check to
201 * make sure that it's not past EOF because we were reading into an
202 * empty file.
203 */
204 if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
205 if (ftype == FILTER_RBANG)
206 (void)close(input[1]);
207
208 if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
209 rval = 1;
210 sp->rptlines[L_ADDED0] += nread;
211 if (ftype == FILTER_READ) {
212 if (fm->lno == 0)
213 rp->lno = nread;
214 else
215 rp->lno += nread;
216 }
217 }
218
219 /*
220 * FILTER_WRITE
221 *
222 * Here we need both a reader and a writer. Temporary files are
223 * expensive and we'd like to avoid disk I/O. Using pipes has the
224 * obvious starvation conditions. It's done as follows:
225 *
226 * fork
227 * child
228 * write lines out
229 * exit
230 * parent
231 * read and display lines
232 * wait for child
233 *
234 * We get away without locking the underlying database because we know
235 * that filter_ldisplay() does not modify it. When the DB code has
236 * locking, we should treat vi as if it were multiple applications
237 * sharing a database, and do the required locking. If necessary a
238 * work-around would be to do explicit locking in the line.c:db_get()
239 * code, based on the flag set here.
240 */
241 if (ftype == FILTER_WRITE) {
242 F_SET(sp->ep, F_MULTILOCK)(((sp->ep)->flags) |= ((0x008)));
243 switch (parent_writer_pid = fork()) {
244 case -1: /* Error. */
245 msgq(sp, M_SYSERR, "fork");
246 (void)close(input[1]);
247 (void)close(output[0]);
248 rval = 1;
249 break;
250 case 0: /* Parent-writer. */
251 /*
252 * Write the selected lines to the write end of the
253 * input pipe. This instance of ifp is closed by
254 * ex_writefp.
255 */
256 (void)close(output[0]);
257 if ((ifp = fdopen(input[1], "w")) == NULL((void *)0))
258 _exit (1);
259 _exit(ex_writefp(sp, "filter",
260 ifp, fm, tm, NULL((void *)0), NULL((void *)0), 1));
261 /* NOTREACHED */
262 default: /* Parent-reader. */
263 (void)close(input[1]);
264 /*
265 * Read the output from the read end of the output
266 * pipe and display it. Filter_ldisplay closes ofp.
267 */
268 if (filter_ldisplay(sp, ofp))
269 rval = 1;
270
271 /* Wait for the parent-writer. */
272 if (proc_wait(sp,
273 parent_writer_pid, "parent-writer", 0, 1))
274 rval = 1;
275 break;
276 }
277 F_CLR(sp->ep, F_MULTILOCK)(((sp->ep)->flags) &= ~((0x008)));
278 }
279
280 /*
281 * FILTER_BANG
282 *
283 * Here we need a temporary file because our database lacks locking.
284 *
285 * XXX
286 * Temporary files are expensive and we'd like to avoid disk I/O.
287 * When the DB code has locking, we should treat vi as if it were
288 * multiple applications sharing a database, and do the required
289 * locking. If necessary a work-around would be to do explicit
290 * locking in the line.c:db_get() code, based on F_MULTILOCK flag set
291 * here.
292 */
293 if (ftype == FILTER_BANG) {
294 /*
295 * Read the output from the read end of the output
296 * pipe. Ex_readfp appends to the MARK and closes
297 * ofp.
298 */
299 if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
300 rval = 1;
301 sp->rptlines[L_ADDED0] += nread;
302
303 /* Delete any lines written to the utility. */
304 if (rval == 0 &&
305 (cut(sp, NULL((void *)0), fm, tm, CUT_LINEMODE0x01) ||
306 del(sp, fm, tm, 1))) {
307 rval = 1;
308 goto uwait;
309 }
310
311 /*
312 * If the filter had no output, we may have just deleted
313 * the cursor. Don't do any real error correction, we'll
314 * try and recover later.
315 */
316 if (rp->lno > 1 && !db_exist(sp, rp->lno))
317 --rp->lno;
318 }
319
320 /*
321 * !!!
322 * Ignore errors on vi file reads, to make reads prettier. It's
323 * completely inconsistent, and historic practice.
324 */
325uwait: return (proc_wait(sp, utility_pid, cmd,
326 ftype == FILTER_READ && F_ISSET(sp, SC_VI)(((sp)->flags) & ((0x00000002))) ? 1 : 0, 0) || rval);
327}
328
329/*
330 * filter_ldisplay --
331 * Display output from a utility.
332 *
333 * !!!
334 * Historically, the characters were passed unmodified to the terminal.
335 * We use the ex print routines to make sure they're printable.
336 */
337static int
338filter_ldisplay(SCR *sp, FILE *fp)
339{
340 size_t len;
341
342 EX_PRIVATE *exp;
343
344 for (exp = EXP(sp)((EX_PRIVATE *)((sp)->ex_private)); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp)(((((sp)->gp)->flags) & ((0x0004))) || (!v_event_get
((sp), ((void *)0), 0, 0x001) && ((((sp)->gp)->
flags) & ((0x0004)))))
;)
345 if (ex_ldisplay(sp, exp->ibp, len, 0, 0))
346 break;
347 if (ferror(fp)(!__isthreaded ? (((fp)->_flags & 0x0040) != 0) : (ferror
)(fp))
)
348 msgq(sp, M_SYSERR, "filter read");
349 (void)fclose(fp);
350 return (0);
351}