GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: exec.c,v 1.68 2016/12/11 17:49:19 millert Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* execute command tree |
||
5 |
*/ |
||
6 |
|||
7 |
#include <sys/stat.h> |
||
8 |
|||
9 |
#include <ctype.h> |
||
10 |
#include <errno.h> |
||
11 |
#include <fcntl.h> |
||
12 |
#include <paths.h> |
||
13 |
#include <stdio.h> |
||
14 |
#include <stdlib.h> |
||
15 |
#include <string.h> |
||
16 |
#include <unistd.h> |
||
17 |
|||
18 |
#include "sh.h" |
||
19 |
#include "c_test.h" |
||
20 |
|||
21 |
/* Does ps4 get parameter substitutions done? */ |
||
22 |
# define PS4_SUBSTITUTE(s) substitute((s), 0) |
||
23 |
|||
24 |
static int comexec(struct op *, struct tbl *volatile, char **, |
||
25 |
int volatile, volatile int *); |
||
26 |
static void scriptexec(struct op *, char **); |
||
27 |
static int call_builtin(struct tbl *, char **); |
||
28 |
static int iosetup(struct ioword *, struct tbl *); |
||
29 |
static int herein(const char *, int); |
||
30 |
static char *do_selectargs(char **, bool); |
||
31 |
static int dbteste_isa(Test_env *, Test_meta); |
||
32 |
static const char *dbteste_getopnd(Test_env *, Test_op, int); |
||
33 |
static int dbteste_eval(Test_env *, Test_op, const char *, const char *, |
||
34 |
int); |
||
35 |
static void dbteste_error(Test_env *, int, const char *); |
||
36 |
|||
37 |
|||
38 |
/* |
||
39 |
* execute command tree |
||
40 |
*/ |
||
41 |
int |
||
42 |
execute(struct op *volatile t, |
||
43 |
volatile int flags, /* if XEXEC don't fork */ |
||
44 |
volatile int *xerrok) /* inform recursive callers in -e mode that |
||
45 |
* short-circuit && or || shouldn't be treated |
||
46 |
* as an error */ |
||
47 |
{ |
||
48 |
10761622 |
int i, dummy = 0, save_xerrok = 0; |
|
49 |
10761622 |
volatile int rv = 0; |
|
50 |
10761622 |
int pv[2]; |
|
51 |
10761622 |
char ** volatile ap; |
|
52 |
char *s, *cp; |
||
53 |
struct ioword **iowp; |
||
54 |
struct tbl *tp = NULL; |
||
55 |
|||
56 |
✓✓ | 10761622 |
if (t == NULL) |
57 |
721022 |
return 0; |
|
58 |
|||
59 |
/* Caller doesn't care if XERROK should propagate. */ |
||
60 |
✓✓ | 10040600 |
if (xerrok == NULL) |
61 |
7208042 |
xerrok = &dummy; |
|
62 |
|||
63 |
/* Is this the end of a pipeline? If so, we want to evaluate the |
||
64 |
* command arguments |
||
65 |
bool eval_done = false; |
||
66 |
if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) { |
||
67 |
eval_done = true; |
||
68 |
tp = eval_execute_args(t, &ap); |
||
69 |
} |
||
70 |
*/ |
||
71 |
✓✓✓✓ ✓✓ |
12783285 |
if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE) |
72 |
491505 |
return exchild(t, flags & ~XTIME, xerrok, -1); /* run in sub-process */ |
|
73 |
|||
74 |
9549095 |
newenv(E_EXEC); |
|
75 |
✓✓ | 9549095 |
if (trap) |
76 |
1513913 |
runtraps(0); |
|
77 |
|||
78 |
✓✓ | 9549080 |
if (t->type == TCOM) { |
79 |
/* Clear subst_exstat before argument expansion. Used by |
||
80 |
* null commands (see comexec() and c_eval()) and by c_set(). |
||
81 |
*/ |
||
82 |
5746434 |
subst_exstat = 0; |
|
83 |
|||
84 |
5746434 |
current_lineno = t->lineno; /* for $LINENO */ |
|
85 |
|||
86 |
/* POSIX says expand command words first, then redirections, |
||
87 |
* and assignments last.. |
||
88 |
*/ |
||
89 |
5746434 |
ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE); |
|
90 |
✓✓ | 5746434 |
if (flags & XTIME) |
91 |
/* Allow option parsing (bizarre, but POSIX) */ |
||
92 |
1358 |
timex_hook(t, &ap); |
|
93 |
✓✓✓✗ |
5769002 |
if (Flag(FXTRACE) && ap[0]) { |
94 |
22568 |
shf_fprintf(shl_out, "%s", |
|
95 |
22568 |
PS4_SUBSTITUTE(str_val(global("PS4")))); |
|
96 |
✓✓ | 141232 |
for (i = 0; ap[i]; i++) |
97 |
96096 |
shf_fprintf(shl_out, "%s%s", ap[i], |
|
98 |
48048 |
ap[i + 1] ? " " : "\n"); |
|
99 |
22568 |
shf_flush(shl_out); |
|
100 |
22568 |
} |
|
101 |
✓✓ | 5746434 |
if (ap[0]) |
102 |
3069498 |
tp = findcom(ap[0], FC_BI|FC_FUNC); |
|
103 |
} |
||
104 |
9548525 |
flags &= ~XTIME; |
|
105 |
|||
106 |
✓✓✓✓ ✗✓ |
27492757 |
if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) { |
107 |
1078167 |
genv->savefd = areallocarray(NULL, NUFILE, sizeof(short), ATEMP); |
|
108 |
/* initialize to not redirected */ |
||
109 |
1078167 |
memset(genv->savefd, 0, NUFILE * sizeof(short)); |
|
110 |
1078167 |
} |
|
111 |
|||
112 |
/* do redirection, to be restored in quitenv() */ |
||
113 |
✓✓ | 9548525 |
if (t->ioact != NULL) |
114 |
✓✓ | 383934 |
for (iowp = t->ioact; *iowp != NULL; iowp++) { |
115 |
✗✓ | 117316 |
if (iosetup(*iowp, tp) < 0) { |
116 |
exstat = rv = 1; |
||
117 |
/* Redirection failures for special commands |
||
118 |
* cause (non-interactive) shell to exit. |
||
119 |
*/ |
||
120 |
if (tp && tp->type == CSHELL && |
||
121 |
(tp->flag & SPEC_BI)) |
||
122 |
errorf(NULL); |
||
123 |
/* Deal with FERREXIT, quitenv(), etc. */ |
||
124 |
goto Break; |
||
125 |
} |
||
126 |
} |
||
127 |
|||
128 |
✓✓✓✓ ✗✓✗✓ ✓✓✗✓ ✗✓✗✓ ✓✓✓✓ ✗✓ |
18671622 |
switch (t->type) { |
129 |
case TCOM: |
||
130 |
5746428 |
rv = comexec(t, tp, ap, flags, xerrok); |
|
131 |
5746428 |
break; |
|
132 |
|||
133 |
case TPAREN: |
||
134 |
1670 |
rv = execute(t->left, flags|XFORK, xerrok); |
|
135 |
1670 |
break; |
|
136 |
|||
137 |
case TPIPE: |
||
138 |
1003516 |
flags |= XFORK; |
|
139 |
1003516 |
flags &= ~XEXEC; |
|
140 |
1003516 |
genv->savefd[0] = savefd(0); |
|
141 |
1003516 |
genv->savefd[1] = savefd(1); |
|
142 |
✓✓ | 4795978 |
while (t->type == TPIPE) { |
143 |
1409192 |
openpipe(pv); |
|
144 |
1409192 |
(void) ksh_dup2(pv[1], 1, false); /* stdout of curr */ |
|
145 |
/* Let exchild() close pv[0] in child |
||
146 |
* (if this isn't done, commands like |
||
147 |
* (: ; cat /etc/termcap) | sleep 1 |
||
148 |
* will hang forever). |
||
149 |
*/ |
||
150 |
1409192 |
exchild(t->left, flags|XPIPEO|XCCLOSE, NULL, pv[0]); |
|
151 |
1409192 |
(void) ksh_dup2(pv[0], 0, false); /* stdin of next */ |
|
152 |
1409192 |
closepipe(pv); |
|
153 |
1409192 |
flags |= XPIPEI; |
|
154 |
1409192 |
t = t->right; |
|
155 |
} |
||
156 |
974012 |
restfd(1, genv->savefd[1]); /* stdout of last */ |
|
157 |
974012 |
genv->savefd[1] = 0; /* no need to re-restore this */ |
|
158 |
/* Let exchild() close 0 in parent, after fork, before wait */ |
||
159 |
974012 |
i = exchild(t, flags|XPCLOSE, xerrok, 0); |
|
160 |
✓✓✓✓ |
1948018 |
if (!(flags&XBGND) && !(flags&XXCOM)) |
161 |
98750 |
rv = i; |
|
162 |
break; |
||
163 |
|||
164 |
case TLIST: |
||
165 |
✓✓ | 10097175 |
while (t->type == TLIST) { |
166 |
4236829 |
execute(t->left, flags & XERROK, NULL); |
|
167 |
4236829 |
t = t->right; |
|
168 |
} |
||
169 |
779930 |
rv = execute(t, flags & XERROK, xerrok); |
|
170 |
779930 |
break; |
|
171 |
|||
172 |
case TCOPROC: |
||
173 |
{ |
||
174 |
sigset_t omask; |
||
175 |
|||
176 |
/* Block sigchild as we are using things changed in the |
||
177 |
* signal handler |
||
178 |
*/ |
||
179 |
sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); |
||
180 |
genv->type = E_ERRH; |
||
181 |
i = sigsetjmp(genv->jbuf, 0); |
||
182 |
if (i) { |
||
183 |
sigprocmask(SIG_SETMASK, &omask, NULL); |
||
184 |
quitenv(NULL); |
||
185 |
unwind(i); |
||
186 |
/* NOTREACHED */ |
||
187 |
} |
||
188 |
/* Already have a (live) co-process? */ |
||
189 |
if (coproc.job && coproc.write >= 0) |
||
190 |
errorf("coprocess already exists"); |
||
191 |
|||
192 |
/* Can we re-use the existing co-process pipe? */ |
||
193 |
coproc_cleanup(true); |
||
194 |
|||
195 |
/* do this before opening pipes, in case these fail */ |
||
196 |
genv->savefd[0] = savefd(0); |
||
197 |
genv->savefd[1] = savefd(1); |
||
198 |
|||
199 |
openpipe(pv); |
||
200 |
if (pv[0] != 0) { |
||
201 |
ksh_dup2(pv[0], 0, false); |
||
202 |
close(pv[0]); |
||
203 |
} |
||
204 |
coproc.write = pv[1]; |
||
205 |
coproc.job = NULL; |
||
206 |
|||
207 |
if (coproc.readw >= 0) |
||
208 |
ksh_dup2(coproc.readw, 1, false); |
||
209 |
else { |
||
210 |
openpipe(pv); |
||
211 |
coproc.read = pv[0]; |
||
212 |
ksh_dup2(pv[1], 1, false); |
||
213 |
coproc.readw = pv[1]; /* closed before first read */ |
||
214 |
coproc.njobs = 0; |
||
215 |
/* create new coprocess id */ |
||
216 |
++coproc.id; |
||
217 |
} |
||
218 |
sigprocmask(SIG_SETMASK, &omask, NULL); |
||
219 |
genv->type = E_EXEC; /* no more need for error handler */ |
||
220 |
|||
221 |
/* exchild() closes coproc.* in child after fork, |
||
222 |
* will also increment coproc.njobs when the |
||
223 |
* job is actually created. |
||
224 |
*/ |
||
225 |
flags &= ~XEXEC; |
||
226 |
exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE, |
||
227 |
NULL, coproc.readw); |
||
228 |
break; |
||
229 |
} |
||
230 |
|||
231 |
case TASYNC: |
||
232 |
/* XXX non-optimal, I think - "(foo &)", forks for (), |
||
233 |
* forks again for async... parent should optimize |
||
234 |
* this to "foo &"... |
||
235 |
*/ |
||
236 |
775 |
rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok); |
|
237 |
775 |
break; |
|
238 |
|||
239 |
case TOR: |
||
240 |
case TAND: |
||
241 |
444954 |
rv = execute(t->left, XERROK, xerrok); |
|
242 |
✓✓ | 444954 |
if ((rv == 0) == (t->type == TAND)) |
243 |
185584 |
rv = execute(t->right, flags & XERROK, xerrok); |
|
244 |
else { |
||
245 |
259370 |
flags |= XERROK; |
|
246 |
259370 |
*xerrok = 1; |
|
247 |
} |
||
248 |
break; |
||
249 |
|||
250 |
case TBANG: |
||
251 |
643 |
rv = !execute(t->right, XERROK, xerrok); |
|
252 |
643 |
flags |= XERROK; |
|
253 |
643 |
*xerrok = 1; |
|
254 |
643 |
break; |
|
255 |
|||
256 |
case TDBRACKET: |
||
257 |
{ |
||
258 |
13234 |
Test_env te; |
|
259 |
|||
260 |
13234 |
te.flags = TEF_DBRACKET; |
|
261 |
13234 |
te.pos.wp = t->args; |
|
262 |
13234 |
te.isa = dbteste_isa; |
|
263 |
13234 |
te.getopnd = dbteste_getopnd; |
|
264 |
13234 |
te.eval = dbteste_eval; |
|
265 |
13234 |
te.error = dbteste_error; |
|
266 |
|||
267 |
13234 |
rv = test_parse(&te); |
|
268 |
break; |
||
269 |
13234 |
} |
|
270 |
|||
271 |
case TFOR: |
||
272 |
case TSELECT: |
||
273 |
{ |
||
274 |
32190 |
volatile bool is_first = true; |
|
275 |
✓✓ | 96564 |
ap = (t->vars != NULL) ? eval(t->vars, DOBLANK|DOGLOB|DOTILDE) : |
276 |
308 |
genv->loc->argv + 1; |
|
277 |
32184 |
genv->type = E_LOOP; |
|
278 |
32368 |
while (1) { |
|
279 |
32758 |
i = sigsetjmp(genv->jbuf, 0); |
|
280 |
✓✓ | 32758 |
if (!i) |
281 |
break; |
||
282 |
✓✓ | 390 |
if ((genv->flags&EF_BRKCONT_PASS) || |
283 |
✓✓ | 366 |
(i != LBREAK && i != LCONTIN)) { |
284 |
quitenv(NULL); |
||
285 |
unwind(i); |
||
286 |
✓✓ | 308 |
} else if (i == LBREAK) { |
287 |
124 |
rv = 0; |
|
288 |
124 |
goto Break; |
|
289 |
} |
||
290 |
} |
||
291 |
32368 |
rv = 0; /* in case of a continue */ |
|
292 |
✓✗ | 32368 |
if (t->type == TFOR) { |
293 |
32368 |
save_xerrok = *xerrok; |
|
294 |
✓✓ | 443657 |
while (*ap != NULL) { |
295 |
190405 |
setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); |
|
296 |
/* undo xerrok in all iterations except the |
||
297 |
* last */ |
||
298 |
190405 |
*xerrok = save_xerrok; |
|
299 |
190405 |
rv = execute(t->left, flags & XERROK, xerrok); |
|
300 |
} |
||
301 |
/* ripple xerrok set at final iteration */ |
||
302 |
} else { /* TSELECT */ |
||
303 |
for (;;) { |
||
304 |
if (!(cp = do_selectargs(ap, is_first))) { |
||
305 |
rv = 1; |
||
306 |
break; |
||
307 |
} |
||
308 |
is_first = false; |
||
309 |
setstr(global(t->str), cp, KSH_UNWIND_ERROR); |
||
310 |
rv = execute(t->left, flags & XERROK, xerrok); |
||
311 |
} |
||
312 |
} |
||
313 |
✗✓✓ | 229490 |
} |
314 |
break; |
||
315 |
|||
316 |
case TWHILE: |
||
317 |
case TUNTIL: |
||
318 |
61475 |
genv->type = E_LOOP; |
|
319 |
62601 |
while (1) { |
|
320 |
63763 |
i = sigsetjmp(genv->jbuf, 0); |
|
321 |
✓✓ | 63763 |
if (!i) |
322 |
break; |
||
323 |
✓✗ | 1162 |
if ((genv->flags&EF_BRKCONT_PASS) || |
324 |
✓✓ | 1162 |
(i != LBREAK && i != LCONTIN)) { |
325 |
quitenv(NULL); |
||
326 |
unwind(i); |
||
327 |
✓✓ | 1132 |
} else if (i == LBREAK) { |
328 |
6 |
rv = 0; |
|
329 |
6 |
goto Break; |
|
330 |
} |
||
331 |
} |
||
332 |
62601 |
rv = 0; /* in case of a continue */ |
|
333 |
✓✓ | 327694 |
while ((execute(t->left, XERROK, NULL) == 0) == (t->type == TWHILE)) |
334 |
102574 |
rv = execute(t->right, flags & XERROK, xerrok); |
|
335 |
break; |
||
336 |
|||
337 |
case TIF: |
||
338 |
case TELIF: |
||
339 |
✓✗ | 882844 |
if (t->right == NULL) |
340 |
break; /* should be error */ |
||
341 |
✓✓ | 2635544 |
rv = execute(t->left, XERROK, NULL) == 0 ? |
342 |
141750 |
execute(t->right->left, flags & XERROK, xerrok) : |
|
343 |
740821 |
execute(t->right->right, flags & XERROK, xerrok); |
|
344 |
870402 |
break; |
|
345 |
|||
346 |
case TCASE: |
||
347 |
15455 |
cp = evalstr(t->str, DOTILDE); |
|
348 |
✓✓✓✗ |
69553 |
for (t = t->left; t != NULL && t->type == TPAT; t = t->right) { |
349 |
✓✓ | 66072 |
for (ap = t->vars; *ap; ap++) { |
350 |
✓✗✓✓ |
50242 |
if ((s = evalstr(*ap, DOTILDE|DOPAT)) && |
351 |
25121 |
gmatch(cp, s, false)) |
|
352 |
goto Found; |
||
353 |
} |
||
354 |
} |
||
355 |
break; |
||
356 |
Found: |
||
357 |
14898 |
rv = execute(t->left, flags & XERROK, xerrok); |
|
358 |
14898 |
break; |
|
359 |
|||
360 |
case TBRACE: |
||
361 |
474009 |
rv = execute(t->left, flags & XERROK, xerrok); |
|
362 |
474009 |
break; |
|
363 |
|||
364 |
case TFUNCT: |
||
365 |
26168 |
rv = define(t->str, t); |
|
366 |
26168 |
break; |
|
367 |
|||
368 |
case TTIME: |
||
369 |
/* Clear XEXEC so nested execute() call doesn't exit |
||
370 |
* (allows "ls -l | time grep foo"). |
||
371 |
*/ |
||
372 |
1358 |
rv = timex(t, flags & ~XEXEC, xerrok); |
|
373 |
1358 |
break; |
|
374 |
|||
375 |
case TEXEC: /* an eval'd TCOM */ |
||
376 |
s = t->args[0]; |
||
377 |
ap = makenv(); |
||
378 |
restoresigs(); |
||
379 |
cleanup_proc_env(); |
||
380 |
execve(t->str, t->args, ap); |
||
381 |
if (errno == ENOEXEC) |
||
382 |
scriptexec(t, ap); |
||
383 |
else |
||
384 |
errorf("%s: %s", s, strerror(errno)); |
||
385 |
} |
||
386 |
Break: |
||
387 |
9222609 |
exstat = rv; |
|
388 |
|||
389 |
9222609 |
quitenv(NULL); /* restores IO */ |
|
390 |
✓✓ | 9222609 |
if ((flags&XEXEC)) |
391 |
unwind(LEXIT); /* exit child */ |
||
392 |
✓✓✓✓ ✓✓ |
10542633 |
if (rv != 0 && !(flags & XERROK) && !*xerrok) { |
393 |
11121 |
trapsig(SIGERR_); |
|
394 |
✓✓ | 11121 |
if (Flag(FERREXIT)) |
395 |
unwind(LERROR); |
||
396 |
} |
||
397 |
9219657 |
return rv; |
|
398 |
10424867 |
} |
|
399 |
|||
400 |
/* |
||
401 |
* execute simple command |
||
402 |
*/ |
||
403 |
|||
404 |
static int |
||
405 |
comexec(struct op *t, struct tbl *volatile tp, char **ap, volatile int flags, |
||
406 |
volatile int *xerrok) |
||
407 |
{ |
||
408 |
int i; |
||
409 |
5746428 |
volatile int rv = 0; |
|
410 |
char *cp; |
||
411 |
char **lastp; |
||
412 |
static struct op texec; /* Must be static (XXX but why?) */ |
||
413 |
int type_flags; |
||
414 |
int keepasn_ok; |
||
415 |
int fcflags = FC_BI|FC_FUNC|FC_PATH; |
||
416 |
int bourne_function_call = 0; |
||
417 |
|||
418 |
/* snag the last argument for $_ XXX not the same as at&t ksh, |
||
419 |
* which only seems to set $_ after a newline (but not in |
||
420 |
* functions/dot scripts, but in interactive and script) - |
||
421 |
* perhaps save last arg here and set it in shell()?. |
||
422 |
*/ |
||
423 |
✓✓✓✓ ✓✓ |
5774289 |
if (!Flag(FSH) && Flag(FTALKING) && *(lastp = ap)) { |
424 |
✓✓ | 4832 |
while (*++lastp) |
425 |
; |
||
426 |
/* setstr() can't fail here */ |
||
427 |
860 |
setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp, |
|
428 |
KSH_RETURN_ERROR); |
||
429 |
860 |
} |
|
430 |
|||
431 |
/* Deal with the shell builtins builtin, exec and command since |
||
432 |
* they can be followed by other commands. This must be done before |
||
433 |
* we know if we should create a local block, which must be done |
||
434 |
* before we can do a path search (in case the assignments change |
||
435 |
* PATH). |
||
436 |
* Odd cases: |
||
437 |
* FOO=bar exec > /dev/null FOO is kept but not exported |
||
438 |
* FOO=bar exec foobar FOO is exported |
||
439 |
* FOO=bar command exec > /dev/null FOO is neither kept nor exported |
||
440 |
* FOO=bar command FOO is neither kept nor exported |
||
441 |
* PATH=... foobar use new PATH in foobar search |
||
442 |
*/ |
||
443 |
keepasn_ok = 1; |
||
444 |
✓✓✓✓ |
14425889 |
while (tp && tp->type == CSHELL) { |
445 |
fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */ |
||
446 |
✗✓ | 2459025 |
if (tp->val.f == c_builtin) { |
447 |
if ((cp = *++ap) == NULL) { |
||
448 |
tp = NULL; |
||
449 |
break; |
||
450 |
} |
||
451 |
tp = findcom(cp, FC_BI); |
||
452 |
if (tp == NULL) |
||
453 |
errorf("builtin: %s: not a builtin", cp); |
||
454 |
continue; |
||
455 |
✓✓ | 2459025 |
} else if (tp->val.f == c_exec) { |
456 |
✓✓ | 36388 |
if (ap[1] == NULL) |
457 |
break; |
||
458 |
6 |
ap++; |
|
459 |
6 |
flags |= XEXEC; |
|
460 |
✓✓ | 2422643 |
} else if (tp->val.f == c_command) { |
461 |
int optc, saw_p = 0; |
||
462 |
|||
463 |
/* Ugly dealing with options in two places (here and |
||
464 |
* in c_command(), but such is life) |
||
465 |
*/ |
||
466 |
144 |
ksh_getopt_reset(&builtin_opt, 0); |
|
467 |
✓✓ | 432 |
while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p') |
468 |
saw_p = 1; |
||
469 |
✓✗ | 144 |
if (optc != EOF) |
470 |
144 |
break; /* command -vV or something */ |
|
471 |
/* don't look for functions */ |
||
472 |
fcflags = FC_BI|FC_PATH; |
||
473 |
if (saw_p) { |
||
474 |
if (Flag(FRESTRICTED)) { |
||
475 |
warningf(true, |
||
476 |
"command -p: restricted"); |
||
477 |
rv = 1; |
||
478 |
goto Leave; |
||
479 |
} |
||
480 |
fcflags |= FC_DEFPATH; |
||
481 |
} |
||
482 |
ap += builtin_opt.optind; |
||
483 |
/* POSIX says special builtins lose their status |
||
484 |
* if accessed using command. |
||
485 |
*/ |
||
486 |
keepasn_ok = 0; |
||
487 |
if (!ap[0]) { |
||
488 |
/* ensure command with no args exits with 0 */ |
||
489 |
subst_exstat = 0; |
||
490 |
break; |
||
491 |
} |
||
492 |
} else |
||
493 |
break; |
||
494 |
6 |
tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC)); |
|
495 |
} |
||
496 |
✓✗✓✓ ✓✓✓✓ |
17495363 |
if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN)))) |
497 |
3208770 |
type_flags = 0; |
|
498 |
else { |
||
499 |
/* create new variable/function block */ |
||
500 |
2537658 |
newblock(); |
|
501 |
/* ksh functions don't keep assignments, POSIX functions do. */ |
||
502 |
✓✗✓✓ ✓✓✓✓ |
7950493 |
if (keepasn_ok && tp && tp->type == CFUNC && |
503 |
473996 |
!(tp->flag & FKSH)) { |
|
504 |
bourne_function_call = 1; |
||
505 |
type_flags = 0; |
||
506 |
471556 |
} else |
|
507 |
type_flags = LOCAL|LOCAL_COPY|EXPORT; |
||
508 |
} |
||
509 |
✗✓ | 5746428 |
if (Flag(FEXPORT)) |
510 |
type_flags |= EXPORT; |
||
511 |
✓✓ | 16843198 |
for (i = 0; t->vars[i]; i++) { |
512 |
2675195 |
cp = evalstr(t->vars[i], DOASNTILDE); |
|
513 |
✗✓ | 2675195 |
if (Flag(FXTRACE)) { |
514 |
if (i == 0) |
||
515 |
shf_fprintf(shl_out, "%s", |
||
516 |
PS4_SUBSTITUTE(str_val(global("PS4")))); |
||
517 |
shf_fprintf(shl_out, "%s%s", cp, |
||
518 |
t->vars[i + 1] ? " " : "\n"); |
||
519 |
if (!t->vars[i + 1]) |
||
520 |
shf_flush(shl_out); |
||
521 |
} |
||
522 |
2675171 |
typeset(cp, type_flags, 0, 0, 0); |
|
523 |
✓✓✓✗ |
2675183 |
if (bourne_function_call && !(type_flags & EXPORT)) |
524 |
12 |
typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0); |
|
525 |
} |
||
526 |
|||
527 |
✓✓ | 5735838 |
if ((cp = *ap) == NULL) { |
528 |
2666352 |
rv = subst_exstat; |
|
529 |
2666352 |
goto Leave; |
|
530 |
✓✓ | 3069486 |
} else if (!tp) { |
531 |
✗✓✗✗ |
136471 |
if (Flag(FRESTRICTED) && strchr(cp, '/')) { |
532 |
warningf(true, "%s: restricted", cp); |
||
533 |
rv = 1; |
||
534 |
goto Leave; |
||
535 |
} |
||
536 |
136471 |
tp = findcom(cp, fcflags); |
|
537 |
136471 |
} |
|
538 |
|||
539 |
✓✓✗✓ ✓ |
8140172 |
switch (tp->type) { |
540 |
case CSHELL: /* shell built-in */ |
||
541 |
2459019 |
rv = call_builtin(tp, ap); |
|
542 |
2459019 |
break; |
|
543 |
|||
544 |
case CFUNC: /* function call */ |
||
545 |
{ |
||
546 |
473996 |
volatile int old_xflag, old_inuse; |
|
547 |
473996 |
const char *volatile old_kshname; |
|
548 |
|||
549 |
✗✓ | 473996 |
if (!(tp->flag & ISSET)) { |
550 |
struct tbl *ftp; |
||
551 |
|||
552 |
if (!tp->u.fpath) { |
||
553 |
if (tp->u2.errno_) { |
||
554 |
warningf(true, |
||
555 |
"%s: can't find function " |
||
556 |
"definition file - %s", |
||
557 |
cp, strerror(tp->u2.errno_)); |
||
558 |
rv = 126; |
||
559 |
} else { |
||
560 |
warningf(true, |
||
561 |
"%s: can't find function " |
||
562 |
"definition file", cp); |
||
563 |
rv = 127; |
||
564 |
} |
||
565 |
break; |
||
566 |
} |
||
567 |
if (include(tp->u.fpath, 0, NULL, 0) < 0) { |
||
568 |
warningf(true, |
||
569 |
"%s: can't open function definition file %s - %s", |
||
570 |
cp, tp->u.fpath, strerror(errno)); |
||
571 |
rv = 127; |
||
572 |
break; |
||
573 |
} |
||
574 |
if (!(ftp = findfunc(cp, hash(cp), false)) || |
||
575 |
!(ftp->flag & ISSET)) { |
||
576 |
warningf(true, |
||
577 |
"%s: function not defined by %s", |
||
578 |
cp, tp->u.fpath); |
||
579 |
rv = 127; |
||
580 |
break; |
||
581 |
} |
||
582 |
tp = ftp; |
||
583 |
} |
||
584 |
|||
585 |
/* ksh functions set $0 to function name, POSIX functions leave |
||
586 |
* $0 unchanged. |
||
587 |
*/ |
||
588 |
473996 |
old_kshname = kshname; |
|
589 |
✓✓ | 473996 |
if (tp->flag & FKSH) |
590 |
2440 |
kshname = ap[0]; |
|
591 |
else |
||
592 |
471556 |
ap[0] = (char *) kshname; |
|
593 |
473996 |
genv->loc->argv = ap; |
|
594 |
✓✓ | 5128030 |
for (i = 0; *ap++ != NULL; i++) |
595 |
; |
||
596 |
473996 |
genv->loc->argc = i - 1; |
|
597 |
/* ksh-style functions handle getopts sanely, |
||
598 |
* bourne/posix functions are insane... |
||
599 |
*/ |
||
600 |
✓✓ | 473996 |
if (tp->flag & FKSH) { |
601 |
2440 |
genv->loc->flags |= BF_DOGETOPTS; |
|
602 |
2440 |
genv->loc->getopts_state = user_opt; |
|
603 |
2440 |
getopts_reset(1); |
|
604 |
2440 |
} |
|
605 |
|||
606 |
518310 |
old_xflag = Flag(FXTRACE); |
|
607 |
518310 |
Flag(FXTRACE) = tp->flag & TRACE ? true : false; |
|
608 |
|||
609 |
518310 |
old_inuse = tp->flag & FINUSE; |
|
610 |
518310 |
tp->flag |= FINUSE; |
|
611 |
|||
612 |
518310 |
genv->type = E_FUNC; |
|
613 |
518310 |
i = sigsetjmp(genv->jbuf, 0); |
|
614 |
✓✓ | 518310 |
if (i == 0) { |
615 |
/* seems odd to pass XERROK here, but at&t ksh does */ |
||
616 |
473996 |
exstat = execute(tp->val.t, flags & XERROK, xerrok); |
|
617 |
i = LRETURN; |
||
618 |
473996 |
} |
|
619 |
458027 |
kshname = old_kshname; |
|
620 |
458027 |
Flag(FXTRACE) = old_xflag; |
|
621 |
458027 |
tp->flag = (tp->flag & ~FINUSE) | old_inuse; |
|
622 |
/* Were we deleted while executing? If so, free the execution |
||
623 |
* tree. todo: Unfortunately, the table entry is never re-used |
||
624 |
* until the lookup table is expanded. |
||
625 |
*/ |
||
626 |
✗✓ | 458027 |
if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) { |
627 |
if (tp->flag & ALLOC) { |
||
628 |
tp->flag &= ~ALLOC; |
||
629 |
tfree(tp->val.t, tp->areap); |
||
630 |
} |
||
631 |
tp->flag = 0; |
||
632 |
} |
||
633 |
✗✓✗✗ ✗✓✗ |
458027 |
switch (i) { |
634 |
case LRETURN: |
||
635 |
case LERROR: |
||
636 |
457761 |
rv = exstat; |
|
637 |
457761 |
break; |
|
638 |
case LINTR: |
||
639 |
case LEXIT: |
||
640 |
case LLEAVE: |
||
641 |
case LSHELL: |
||
642 |
quitenv(NULL); |
||
643 |
unwind(i); |
||
644 |
/* NOTREACHED */ |
||
645 |
default: |
||
646 |
quitenv(NULL); |
||
647 |
internal_errorf(1, "CFUNC %d", i); |
||
648 |
} |
||
649 |
457761 |
break; |
|
650 |
457761 |
} |
|
651 |
|||
652 |
case CEXEC: /* executable command */ |
||
653 |
case CTALIAS: /* tracked alias */ |
||
654 |
✓✓ | 136471 |
if (!(tp->flag&ISSET)) { |
655 |
/* errno_ will be set if the named command was found |
||
656 |
* but could not be executed (permissions, no execute |
||
657 |
* bit, directory, etc). Print out a (hopefully) |
||
658 |
* useful error message and set the exit status to 126. |
||
659 |
*/ |
||
660 |
✗✓ | 63 |
if (tp->u2.errno_) { |
661 |
warningf(true, "%s: cannot execute - %s", cp, |
||
662 |
strerror(tp->u2.errno_)); |
||
663 |
rv = 126; /* POSIX */ |
||
664 |
} else { |
||
665 |
63 |
warningf(true, "%s: not found", cp); |
|
666 |
63 |
rv = 127; |
|
667 |
} |
||
668 |
break; |
||
669 |
} |
||
670 |
|||
671 |
✓✓ | 136408 |
if (!Flag(FSH)) { |
672 |
/* set $_ to program's full path */ |
||
673 |
/* setstr() can't fail here */ |
||
674 |
962 |
setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0), |
|
675 |
481 |
tp->val.s, KSH_RETURN_ERROR); |
|
676 |
481 |
} |
|
677 |
|||
678 |
✗✓ | 136408 |
if (flags&XEXEC) { |
679 |
j_exit(); |
||
680 |
if (!(flags&XBGND) || Flag(FMONITOR)) { |
||
681 |
setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG); |
||
682 |
setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG); |
||
683 |
} |
||
684 |
} |
||
685 |
|||
686 |
/* to fork we set up a TEXEC node and call execute */ |
||
687 |
136408 |
texec.type = TEXEC; |
|
688 |
136408 |
texec.left = t; /* for tprint */ |
|
689 |
136408 |
texec.str = tp->val.s; |
|
690 |
136408 |
texec.args = ap; |
|
691 |
136408 |
rv = exchild(&texec, flags, xerrok, -1); |
|
692 |
136408 |
break; |
|
693 |
} |
||
694 |
Leave: |
||
695 |
✓✓ | 5673241 |
if (flags & XEXEC) { |
696 |
exstat = rv; |
||
697 |
unwind(LLEAVE); |
||
698 |
} |
||
699 |
5641204 |
return rv; |
|
700 |
5641204 |
} |
|
701 |
|||
702 |
static void |
||
703 |
scriptexec(struct op *tp, char **ap) |
||
704 |
{ |
||
705 |
char *shell; |
||
706 |
|||
707 |
shell = str_val(global("EXECSHELL")); |
||
708 |
if (shell && *shell) |
||
709 |
shell = search(shell, path, X_OK, NULL); |
||
710 |
if (!shell || !*shell) |
||
711 |
shell = _PATH_BSHELL; |
||
712 |
|||
713 |
*tp->args-- = tp->str; |
||
714 |
*tp->args = shell; |
||
715 |
|||
716 |
execve(tp->args[0], tp->args, ap); |
||
717 |
|||
718 |
/* report both the program that was run and the bogus shell */ |
||
719 |
errorf("%s: %s: %s", tp->str, shell, strerror(errno)); |
||
720 |
} |
||
721 |
|||
722 |
int |
||
723 |
shcomexec(char **wp) |
||
724 |
{ |
||
725 |
struct tbl *tp; |
||
726 |
|||
727 |
1467990 |
tp = ktsearch(&builtins, *wp, hash(*wp)); |
|
728 |
✗✓ | 733995 |
if (tp == NULL) |
729 |
internal_errorf(1, "shcomexec: %s", *wp); |
||
730 |
733995 |
return call_builtin(tp, wp); |
|
731 |
} |
||
732 |
|||
733 |
/* |
||
734 |
* Search function tables for a function. If create set, a table entry |
||
735 |
* is created if none is found. |
||
736 |
*/ |
||
737 |
struct tbl * |
||
738 |
findfunc(const char *name, unsigned int h, int create) |
||
739 |
{ |
||
740 |
struct block *l; |
||
741 |
struct tbl *tp = NULL; |
||
742 |
|||
743 |
✓✓ | 19236833 |
for (l = genv->loc; l; l = l->next) { |
744 |
6121360 |
tp = ktsearch(&l->funs, name, h); |
|
745 |
✓✓ | 6121360 |
if (tp) |
746 |
break; |
||
747 |
✓✓ | 5647302 |
if (!l->next && create) { |
748 |
26156 |
tp = ktenter(&l->funs, name, h); |
|
749 |
26156 |
tp->flag = DEFINED; |
|
750 |
26156 |
tp->type = CFUNC; |
|
751 |
26156 |
tp->val.t = NULL; |
|
752 |
26156 |
break; |
|
753 |
} |
||
754 |
} |
||
755 |
2664847 |
return tp; |
|
756 |
} |
||
757 |
|||
758 |
/* |
||
759 |
* define function. Returns 1 if function is being undefined (t == 0) and |
||
760 |
* function did not exist, returns 0 otherwise. |
||
761 |
*/ |
||
762 |
int |
||
763 |
define(const char *name, struct op *t) |
||
764 |
{ |
||
765 |
struct tbl *tp; |
||
766 |
int was_set = 0; |
||
767 |
|||
768 |
52348 |
while (1) { |
|
769 |
26174 |
tp = findfunc(name, hash(name), true); |
|
770 |
|||
771 |
✓✓ | 26174 |
if (tp->flag & ISSET) |
772 |
18 |
was_set = 1; |
|
773 |
/* If this function is currently being executed, we zap this |
||
774 |
* table entry so findfunc() won't see it |
||
775 |
*/ |
||
776 |
✗✓ | 26174 |
if (tp->flag & FINUSE) { |
777 |
tp->name[0] = '\0'; |
||
778 |
tp->flag &= ~DEFINED; /* ensure it won't be found */ |
||
779 |
tp->flag |= FDELETE; |
||
780 |
} else |
||
781 |
break; |
||
782 |
} |
||
783 |
|||
784 |
✓✓ | 26174 |
if (tp->flag & ALLOC) { |
785 |
18 |
tp->flag &= ~(ISSET|ALLOC); |
|
786 |
18 |
tfree(tp->val.t, tp->areap); |
|
787 |
18 |
} |
|
788 |
|||
789 |
✓✓ | 26174 |
if (t == NULL) { /* undefine */ |
790 |
6 |
ktdelete(tp); |
|
791 |
6 |
return was_set ? 0 : 1; |
|
792 |
} |
||
793 |
|||
794 |
26168 |
tp->val.t = tcopy(t->left, tp->areap); |
|
795 |
26168 |
tp->flag |= (ISSET|ALLOC); |
|
796 |
✓✓ | 26168 |
if (t->u.ksh_func) |
797 |
60 |
tp->flag |= FKSH; |
|
798 |
|||
799 |
26168 |
return 0; |
|
800 |
26174 |
} |
|
801 |
|||
802 |
/* |
||
803 |
* add builtin |
||
804 |
*/ |
||
805 |
void |
||
806 |
builtin(const char *name, int (*func) (char **)) |
||
807 |
{ |
||
808 |
struct tbl *tp; |
||
809 |
int flag; |
||
810 |
|||
811 |
/* see if any flags should be set for this builtin */ |
||
812 |
13828716 |
for (flag = 0; ; name++) { |
|
813 |
✓✓ | 9428670 |
if (*name == '=') /* command does variable assignment */ |
814 |
1885734 |
flag |= KEEPASN; |
|
815 |
✓✓ | 7542936 |
else if (*name == '*') /* POSIX special builtin */ |
816 |
1571445 |
flag |= SPEC_BI; |
|
817 |
✓✓ | 5971491 |
else if (*name == '+') /* POSIX regular builtin */ |
818 |
1571445 |
flag |= REG_BI; |
|
819 |
else |
||
820 |
break; |
||
821 |
} |
||
822 |
|||
823 |
4400046 |
tp = ktenter(&builtins, name, hash(name)); |
|
824 |
4400046 |
tp->flag = DEFINED | flag; |
|
825 |
4400046 |
tp->type = CSHELL; |
|
826 |
4400046 |
tp->val.f = func; |
|
827 |
4400046 |
} |
|
828 |
|||
829 |
/* |
||
830 |
* find command |
||
831 |
* either function, hashed command, or built-in (in that order) |
||
832 |
*/ |
||
833 |
struct tbl * |
||
834 |
findcom(const char *name, int flags) |
||
835 |
{ |
||
836 |
static struct tbl temp; |
||
837 |
6412468 |
unsigned int h = hash(name); |
|
838 |
struct tbl *tp = NULL, *tbi; |
||
839 |
3206234 |
int insert = Flag(FTRACKALL); /* insert if not found */ |
|
840 |
char *fpath; /* for function autoloading */ |
||
841 |
char *npath; |
||
842 |
|||
843 |
✓✓ | 3206234 |
if (strchr(name, '/') != NULL) { |
844 |
insert = 0; |
||
845 |
/* prevent FPATH search below */ |
||
846 |
140962 |
flags &= ~FC_FUNC; |
|
847 |
140962 |
goto Search; |
|
848 |
} |
||
849 |
✓✓ | 9195744 |
tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL; |
850 |
/* POSIX says special builtins first, then functions, then |
||
851 |
* POSIX regular builtins, then search path... |
||
852 |
*/ |
||
853 |
✓✓✓✓ |
5524417 |
if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI)) |
854 |
426527 |
tp = tbi; |
|
855 |
✓✓✓✓ |
5704017 |
if (!tp && (flags & FC_FUNC)) { |
856 |
2638673 |
tp = findfunc(name, h, false); |
|
857 |
✓✓✗✓ |
3112713 |
if (tp && !(tp->flag & ISSET)) { |
858 |
if ((fpath = str_val(global("FPATH"))) == null) { |
||
859 |
tp->u.fpath = NULL; |
||
860 |
tp->u2.errno_ = 0; |
||
861 |
} else |
||
862 |
tp->u.fpath = search(name, fpath, R_OK, |
||
863 |
&tp->u2.errno_); |
||
864 |
} |
||
865 |
} |
||
866 |
✓✓✓✓ ✓✓ |
7262547 |
if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI)) |
867 |
30101 |
tp = tbi; |
|
868 |
/* todo: posix says non-special/non-regular builtins must |
||
869 |
* be triggered by some user-controllable means like a |
||
870 |
* special directory in PATH. Requires modifications to |
||
871 |
* the search() function. Tracked aliases should be |
||
872 |
* modified to allow tracking of builtin commands. |
||
873 |
* This should be under control of the FPOSIX flag. |
||
874 |
* If this is changed, also change c_whence... |
||
875 |
*/ |
||
876 |
✓✓✓✓ |
5199876 |
if (!tp && (flags & FC_UNREGBI) && tbi) |
877 |
2002469 |
tp = tbi; |
|
878 |
✓✓✓✓ ✓✓ |
3263525 |
if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) { |
879 |
66106 |
tp = ktsearch(&taliases, name, h); |
|
880 |
✓✓✓✓ ✗✓ |
127906 |
if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) { |
881 |
if (tp->flag & ALLOC) { |
||
882 |
tp->flag &= ~ALLOC; |
||
883 |
afree(tp->val.s, APERM); |
||
884 |
} |
||
885 |
tp->flag &= ~ISSET; |
||
886 |
} |
||
887 |
} |
||
888 |
|||
889 |
Search: |
||
890 |
✓✓✓✓ ✓✓✓✓ |
6498292 |
if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) && |
891 |
260505 |
(flags & FC_PATH)) { |
|
892 |
✓✓ | 124022 |
if (!tp) { |
893 |
✓✓✓✓ |
104312 |
if (insert && !(flags & FC_DEFPATH)) { |
894 |
16894 |
tp = ktenter(&taliases, name, h); |
|
895 |
tp->type = CTALIAS; |
||
896 |
16894 |
} else { |
|
897 |
tp = &temp; |
||
898 |
tp->type = CEXEC; |
||
899 |
} |
||
900 |
87406 |
tp->flag = DEFINED; /* make ~ISSET */ |
|
901 |
87406 |
} |
|
902 |
248044 |
npath = search(name, flags & FC_DEFPATH ? def_path : path, |
|
903 |
124022 |
X_OK, &tp->u2.errno_); |
|
904 |
✓✓ | 124022 |
if (npath) { |
905 |
✓✓ | 123877 |
if (tp == &temp) { |
906 |
70470 |
tp->val.s = npath; |
|
907 |
70470 |
} else { |
|
908 |
53407 |
tp->val.s = str_save(npath, APERM); |
|
909 |
✓✗ | 53407 |
if (npath != name) |
910 |
53407 |
afree(npath, ATEMP); |
|
911 |
} |
||
912 |
123877 |
tp->flag |= ISSET|ALLOC; |
|
913 |
✓✓✗✗ |
124022 |
} else if ((flags & FC_FUNC) && |
914 |
✗✓ | 55 |
(fpath = str_val(global("FPATH"))) != null && |
915 |
(npath = search(name, fpath, R_OK, |
||
916 |
&tp->u2.errno_)) != NULL) { |
||
917 |
/* An undocumented feature of at&t ksh is that it |
||
918 |
* searches FPATH if a command is not found, even |
||
919 |
* if the command hasn't been set up as an autoloaded |
||
920 |
* function (ie, no typeset -uf). |
||
921 |
*/ |
||
922 |
tp = &temp; |
||
923 |
tp->type = CFUNC; |
||
924 |
tp->flag = DEFINED; /* make ~ISSET */ |
||
925 |
tp->u.fpath = npath; |
||
926 |
} |
||
927 |
} |
||
928 |
3206234 |
return tp; |
|
929 |
} |
||
930 |
|||
931 |
/* |
||
932 |
* flush executable commands with relative paths |
||
933 |
*/ |
||
934 |
void |
||
935 |
flushcom(int all) /* just relative or all */ |
||
936 |
{ |
||
937 |
struct tbl *tp; |
||
938 |
465698 |
struct tstate ts; |
|
939 |
|||
940 |
✓✓ | 1305528 |
for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; ) |
941 |
✓✓✓✓ ✗✓ |
420209 |
if ((tp->flag&ISSET) && (all || tp->val.s[0] != '/')) { |
942 |
✓✗ | 32 |
if (tp->flag&ALLOC) { |
943 |
32 |
tp->flag &= ~(ALLOC|ISSET); |
|
944 |
32 |
afree(tp->val.s, APERM); |
|
945 |
32 |
} |
|
946 |
32 |
tp->flag &= ~ISSET; |
|
947 |
32 |
} |
|
948 |
232849 |
} |
|
949 |
|||
950 |
/* Check if path is something we want to find. Returns -1 for failure. */ |
||
951 |
int |
||
952 |
search_access(const char *path, int mode, |
||
953 |
int *errnop) /* set if candidate found, but not suitable */ |
||
954 |
{ |
||
955 |
int ret, err = 0; |
||
956 |
545672 |
struct stat statb; |
|
957 |
|||
958 |
✓✓ | 272836 |
if (stat(path, &statb) < 0) |
959 |
147570 |
return -1; |
|
960 |
125266 |
ret = access(path, mode); |
|
961 |
✗✓ | 125266 |
if (ret < 0) |
962 |
err = errno; /* File exists, but we can't access it */ |
||
963 |
✓✓✓✗ ✗✓ |
373026 |
else if (mode == X_OK && (!S_ISREG(statb.st_mode) || |
964 |
123880 |
!(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { |
|
965 |
/* This 'cause access() says root can execute everything */ |
||
966 |
ret = -1; |
||
967 |
err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; |
||
968 |
} |
||
969 |
✗✓✗✗ |
125266 |
if (err && errnop && !*errnop) |
970 |
*errnop = err; |
||
971 |
125266 |
return ret; |
|
972 |
272836 |
} |
|
973 |
|||
974 |
/* |
||
975 |
* search for command with PATH |
||
976 |
*/ |
||
977 |
char * |
||
978 |
search(const char *name, const char *path, |
||
979 |
int mode, /* R_OK or X_OK */ |
||
980 |
int *errnop) /* set if candidate found, but not suitable */ |
||
981 |
{ |
||
982 |
const char *sp, *p; |
||
983 |
char *xp; |
||
984 |
250840 |
XString xs; |
|
985 |
int namelen; |
||
986 |
|||
987 |
✓✗ | 125420 |
if (errnop) |
988 |
125420 |
*errnop = 0; |
|
989 |
✓✓ | 125420 |
if (strchr(name, '/')) { |
990 |
✓✓ | 71894 |
if (search_access(name, mode, errnop) == 0) |
991 |
71840 |
return (char *) name; |
|
992 |
54 |
return NULL; |
|
993 |
} |
||
994 |
|||
995 |
53526 |
namelen = strlen(name) + 1; |
|
996 |
53526 |
Xinit(xs, xp, 128, ATEMP); |
|
997 |
|||
998 |
sp = path; |
||
999 |
✓✓ | 402084 |
while (sp != NULL) { |
1000 |
xp = Xstring(xs, xp); |
||
1001 |
✓✓ | 200939 |
if (!(p = strchr(sp, ':'))) |
1002 |
216 |
p = sp + strlen(sp); |
|
1003 |
✓✗ | 200939 |
if (p != sp) { |
1004 |
✗✓ | 200939 |
XcheckN(xs, xp, p - sp); |
1005 |
200939 |
memcpy(xp, sp, p - sp); |
|
1006 |
200939 |
xp += p - sp; |
|
1007 |
200939 |
*xp++ = '/'; |
|
1008 |
200939 |
} |
|
1009 |
sp = p; |
||
1010 |
✗✓ | 200939 |
XcheckN(xs, xp, namelen); |
1011 |
200939 |
memcpy(xp, name, namelen); |
|
1012 |
✓✓ | 200939 |
if (search_access(Xstring(xs, xp), mode, errnop) == 0) |
1013 |
53423 |
return Xclose(xs, xp + namelen); |
|
1014 |
147516 |
if (*sp++ == '\0') |
|
1015 |
sp = NULL; |
||
1016 |
} |
||
1017 |
103 |
Xfree(xs, xp); |
|
1018 |
103 |
return NULL; |
|
1019 |
125420 |
} |
|
1020 |
|||
1021 |
static int |
||
1022 |
call_builtin(struct tbl *tp, char **wp) |
||
1023 |
{ |
||
1024 |
int rv; |
||
1025 |
|||
1026 |
6386028 |
builtin_argv0 = wp[0]; |
|
1027 |
3193014 |
builtin_flag = tp->flag; |
|
1028 |
3193014 |
shf_reopen(1, SHF_WR, shl_stdout); |
|
1029 |
3193014 |
shl_stdout_ok = 1; |
|
1030 |
3193014 |
ksh_getopt_reset(&builtin_opt, GF_ERROR); |
|
1031 |
3193014 |
rv = (*tp->val.f)(wp); |
|
1032 |
3193014 |
shf_flush(shl_stdout); |
|
1033 |
3193014 |
shl_stdout_ok = 0; |
|
1034 |
3193014 |
builtin_flag = 0; |
|
1035 |
3193014 |
builtin_argv0 = NULL; |
|
1036 |
3193014 |
return rv; |
|
1037 |
} |
||
1038 |
|||
1039 |
/* |
||
1040 |
* set up redirection, saving old fd's in e->savefd |
||
1041 |
*/ |
||
1042 |
static int |
||
1043 |
iosetup(struct ioword *iop, struct tbl *tp) |
||
1044 |
{ |
||
1045 |
int u = -1; |
||
1046 |
234644 |
char *cp = iop->name; |
|
1047 |
117322 |
int iotype = iop->flag & IOTYPE; |
|
1048 |
int do_open = 1, do_close = 0, flags = 0; |
||
1049 |
117322 |
struct ioword iotmp; |
|
1050 |
117322 |
struct stat statb; |
|
1051 |
|||
1052 |
✓✓ | 117322 |
if (iotype != IOHERE) |
1053 |
115244 |
cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); |
|
1054 |
|||
1055 |
/* Used for tracing and error messages to print expanded cp */ |
||
1056 |
117322 |
iotmp = *iop; |
|
1057 |
117322 |
iotmp.name = (iotype == IOHERE) ? NULL : cp; |
|
1058 |
117322 |
iotmp.flag |= IONAMEXP; |
|
1059 |
|||
1060 |
✗✓ | 117322 |
if (Flag(FXTRACE)) |
1061 |
shellf("%s%s\n", |
||
1062 |
PS4_SUBSTITUTE(str_val(global("PS4"))), |
||
1063 |
snptreef(NULL, 32, "%R", &iotmp)); |
||
1064 |
|||
1065 |
✓✓✓✗ ✓✓✓ |
234630 |
switch (iotype) { |
1066 |
case IOREAD: |
||
1067 |
flags = O_RDONLY; |
||
1068 |
8554 |
break; |
|
1069 |
|||
1070 |
case IOCAT: |
||
1071 |
flags = O_WRONLY | O_APPEND | O_CREAT; |
||
1072 |
51424 |
break; |
|
1073 |
|||
1074 |
case IOWRITE: |
||
1075 |
flags = O_WRONLY | O_CREAT | O_TRUNC; |
||
1076 |
/* The stat() is here to allow redirections to |
||
1077 |
* things like /dev/null without error. |
||
1078 |
*/ |
||
1079 |
✗✓✗✗ ✗✗ |
14713 |
if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) && |
1080 |
(stat(cp, &statb) < 0 || S_ISREG(statb.st_mode))) |
||
1081 |
flags |= O_EXCL; |
||
1082 |
break; |
||
1083 |
|||
1084 |
case IORDWR: |
||
1085 |
flags = O_RDWR | O_CREAT; |
||
1086 |
break; |
||
1087 |
|||
1088 |
case IOHERE: |
||
1089 |
do_open = 0; |
||
1090 |
/* herein() returns -2 if error has been printed */ |
||
1091 |
2078 |
u = herein(iop->heredoc, iop->flag & IOEVAL); |
|
1092 |
/* cp may have wrong name */ |
||
1093 |
2078 |
break; |
|
1094 |
|||
1095 |
case IODUP: |
||
1096 |
{ |
||
1097 |
40553 |
const char *emsg; |
|
1098 |
|||
1099 |
do_open = 0; |
||
1100 |
✗✓✗✗ |
40553 |
if (*cp == '-' && !cp[1]) { |
1101 |
u = 1009; /* prevent error return below */ |
||
1102 |
do_close = 1; |
||
1103 |
✗✓ | 81106 |
} else if ((u = check_fd(cp, |
1104 |
40553 |
X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK), |
|
1105 |
40553 |
&emsg)) < 0) { |
|
1106 |
warningf(true, "%s: %s", |
||
1107 |
snptreef(NULL, 32, "%R", &iotmp), emsg); |
||
1108 |
return -1; |
||
1109 |
} |
||
1110 |
✓✓ | 40553 |
if (u == iop->unit) |
1111 |
8 |
return 0; /* "dup from" == "dup to" */ |
|
1112 |
40545 |
break; |
|
1113 |
✓✓ | 40553 |
} |
1114 |
} |
||
1115 |
|||
1116 |
✓✓ | 117308 |
if (do_open) { |
1117 |
✗✓✗✗ |
74691 |
if (Flag(FRESTRICTED) && (flags & O_CREAT)) { |
1118 |
warningf(true, "%s: restricted", cp); |
||
1119 |
return -1; |
||
1120 |
} |
||
1121 |
74691 |
u = open(cp, flags, 0666); |
|
1122 |
74691 |
} |
|
1123 |
✗✓ | 117308 |
if (u < 0) { |
1124 |
/* herein() may already have printed message */ |
||
1125 |
if (u == -1) |
||
1126 |
warningf(true, "cannot %s %s: %s", |
||
1127 |
iotype == IODUP ? "dup" : |
||
1128 |
(iotype == IOREAD || iotype == IOHERE) ? |
||
1129 |
"open" : "create", cp, strerror(errno)); |
||
1130 |
return -1; |
||
1131 |
} |
||
1132 |
/* Do not save if it has already been redirected (i.e. "cat >x >y"). */ |
||
1133 |
✓✓ | 117308 |
if (genv->savefd[iop->unit] == 0) { |
1134 |
/* If these are the same, it means unit was previously closed */ |
||
1135 |
✓✓ | 117302 |
if (u == iop->unit) |
1136 |
1 |
genv->savefd[iop->unit] = -1; |
|
1137 |
else |
||
1138 |
/* c_exec() assumes e->savefd[fd] set for any |
||
1139 |
* redirections. Ask savefd() not to close iop->unit; |
||
1140 |
* this allows error messages to be seen if iop->unit |
||
1141 |
* is 2; also means we can't lose the fd (eg, both |
||
1142 |
* dup2 below and dup2 in restfd() failing). |
||
1143 |
*/ |
||
1144 |
117301 |
genv->savefd[iop->unit] = savefd(iop->unit); |
|
1145 |
117302 |
} |
|
1146 |
|||
1147 |
✗✓ | 117308 |
if (do_close) |
1148 |
close(iop->unit); |
||
1149 |
✓✓ | 117308 |
else if (u != iop->unit) { |
1150 |
✗✓ | 117307 |
if (ksh_dup2(u, iop->unit, true) < 0) { |
1151 |
warningf(true, |
||
1152 |
"could not finish (dup) redirection %s: %s", |
||
1153 |
snptreef(NULL, 32, "%R", &iotmp), |
||
1154 |
strerror(errno)); |
||
1155 |
if (iotype != IODUP) |
||
1156 |
close(u); |
||
1157 |
return -1; |
||
1158 |
} |
||
1159 |
✓✓ | 117307 |
if (iotype != IODUP) |
1160 |
76762 |
close(u); |
|
1161 |
/* Touching any co-process fd in an empty exec |
||
1162 |
* causes the shell to close its copies |
||
1163 |
*/ |
||
1164 |
✓✓✓✓ ✓✓ |
120443 |
else if (tp && tp->type == CSHELL && tp->val.f == c_exec) { |
1165 |
✗✓ | 39298 |
if (iop->flag & IORDUP) /* possible exec <&p */ |
1166 |
coproc_read_close(u); |
||
1167 |
else /* possible exec >&p */ |
||
1168 |
39298 |
coproc_write_close(u); |
|
1169 |
} |
||
1170 |
} |
||
1171 |
✓✓ | 117308 |
if (u == 2) /* Clear any write errors */ |
1172 |
1456 |
shf_reopen(2, SHF_WR, shl_out); |
|
1173 |
117308 |
return 0; |
|
1174 |
117316 |
} |
|
1175 |
|||
1176 |
/* |
||
1177 |
* open here document temp file. |
||
1178 |
* if unquoted here, expand here temp file into second temp file. |
||
1179 |
*/ |
||
1180 |
static int |
||
1181 |
herein(const char *content, int sub) |
||
1182 |
{ |
||
1183 |
4156 |
volatile int fd = -1; |
|
1184 |
2078 |
struct source *s, *volatile osource; |
|
1185 |
2078 |
struct shf *volatile shf; |
|
1186 |
struct temp *h; |
||
1187 |
int i; |
||
1188 |
|||
1189 |
/* ksh -c 'cat << EOF' can cause this... */ |
||
1190 |
✗✓ | 2078 |
if (content == NULL) { |
1191 |
warningf(true, "here document missing"); |
||
1192 |
return -2; /* special to iosetup(): don't print error */ |
||
1193 |
} |
||
1194 |
|||
1195 |
/* Create temp file to hold content (done before newenv so temp |
||
1196 |
* doesn't get removed too soon). |
||
1197 |
*/ |
||
1198 |
2078 |
h = maketemp(ATEMP, TT_HEREDOC_EXP, &genv->temps); |
|
1199 |
✓✗✗✓ |
4156 |
if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) { |
1200 |
warningf(true, "can't %s temporary file %s: %s", |
||
1201 |
!shf ? "create" : "open", |
||
1202 |
h->name, strerror(errno)); |
||
1203 |
if (shf) |
||
1204 |
shf_close(shf); |
||
1205 |
return -2 /* special to iosetup(): don't print error */; |
||
1206 |
} |
||
1207 |
|||
1208 |
2084 |
osource = source; |
|
1209 |
2084 |
newenv(E_ERRH); |
|
1210 |
2084 |
i = sigsetjmp(genv->jbuf, 0); |
|
1211 |
✓✓ | 2084 |
if (i) { |
1212 |
6 |
source = osource; |
|
1213 |
6 |
quitenv(shf); |
|
1214 |
6 |
close(fd); |
|
1215 |
6 |
return -2; /* special to iosetup(): don't print error */ |
|
1216 |
} |
||
1217 |
✓✓ | 2078 |
if (sub) { |
1218 |
/* Do substitutions on the content of heredoc */ |
||
1219 |
1996 |
s = pushs(SSTRING, ATEMP); |
|
1220 |
1996 |
s->start = s->str = content; |
|
1221 |
1996 |
source = s; |
|
1222 |
✗✓ | 1996 |
if (yylex(ONEWORD|HEREDOC) != LWORD) |
1223 |
internal_errorf(1, "herein: yylex"); |
||
1224 |
1996 |
source = osource; |
|
1225 |
1996 |
shf_puts(evalstr(yylval.cp, 0), shf); |
|
1226 |
1996 |
} else |
|
1227 |
82 |
shf_puts(content, shf); |
|
1228 |
|||
1229 |
2072 |
quitenv(NULL); |
|
1230 |
|||
1231 |
✗✓ | 2072 |
if (shf_close(shf) == EOF) { |
1232 |
close(fd); |
||
1233 |
warningf(true, "error writing %s: %s", h->name, |
||
1234 |
strerror(errno)); |
||
1235 |
return -2; /* special to iosetup(): don't print error */ |
||
1236 |
} |
||
1237 |
|||
1238 |
2072 |
return fd; |
|
1239 |
2072 |
} |
|
1240 |
|||
1241 |
#ifdef EDIT |
||
1242 |
/* |
||
1243 |
* ksh special - the select command processing section |
||
1244 |
* print the args in column form - assuming that we can |
||
1245 |
*/ |
||
1246 |
static char * |
||
1247 |
do_selectargs(char **ap, bool print_menu) |
||
1248 |
{ |
||
1249 |
static const char *const read_args[] = { |
||
1250 |
"read", "-r", "REPLY", NULL |
||
1251 |
}; |
||
1252 |
const char *errstr; |
||
1253 |
char *s; |
||
1254 |
int i, argct; |
||
1255 |
|||
1256 |
for (argct = 0; ap[argct]; argct++) |
||
1257 |
; |
||
1258 |
while (1) { |
||
1259 |
/* Menu is printed if |
||
1260 |
* - this is the first time around the select loop |
||
1261 |
* - the user enters a blank line |
||
1262 |
* - the REPLY parameter is empty |
||
1263 |
*/ |
||
1264 |
if (print_menu || !*str_val(global("REPLY"))) |
||
1265 |
pr_menu(ap); |
||
1266 |
shellf("%s", str_val(global("PS3"))); |
||
1267 |
if (call_builtin(findcom("read", FC_BI), (char **) read_args)) |
||
1268 |
return NULL; |
||
1269 |
s = str_val(global("REPLY")); |
||
1270 |
if (*s) { |
||
1271 |
i = strtonum(s, 1, argct, &errstr); |
||
1272 |
if (errstr) |
||
1273 |
return null; |
||
1274 |
return ap[i - 1]; |
||
1275 |
} |
||
1276 |
print_menu = 1; |
||
1277 |
} |
||
1278 |
} |
||
1279 |
|||
1280 |
struct select_menu_info { |
||
1281 |
char *const *args; |
||
1282 |
int arg_width; |
||
1283 |
int num_width; |
||
1284 |
}; |
||
1285 |
|||
1286 |
static char *select_fmt_entry(void *arg, int i, char *buf, int buflen); |
||
1287 |
|||
1288 |
/* format a single select menu item */ |
||
1289 |
static char * |
||
1290 |
select_fmt_entry(void *arg, int i, char *buf, int buflen) |
||
1291 |
{ |
||
1292 |
struct select_menu_info *smi = (struct select_menu_info *) arg; |
||
1293 |
|||
1294 |
shf_snprintf(buf, buflen, "%*d) %s", |
||
1295 |
smi->num_width, i + 1, smi->args[i]); |
||
1296 |
return buf; |
||
1297 |
} |
||
1298 |
|||
1299 |
/* |
||
1300 |
* print a select style menu |
||
1301 |
*/ |
||
1302 |
int |
||
1303 |
pr_menu(char *const *ap) |
||
1304 |
{ |
||
1305 |
struct select_menu_info smi; |
||
1306 |
char *const *pp; |
||
1307 |
int nwidth, dwidth; |
||
1308 |
int i, n; |
||
1309 |
|||
1310 |
/* Width/column calculations were done once and saved, but this |
||
1311 |
* means select can't be used recursively so we re-calculate each |
||
1312 |
* time (could save in a structure that is returned, but its probably |
||
1313 |
* not worth the bother). |
||
1314 |
*/ |
||
1315 |
|||
1316 |
/* |
||
1317 |
* get dimensions of the list |
||
1318 |
*/ |
||
1319 |
for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) { |
||
1320 |
i = strlen(*pp); |
||
1321 |
nwidth = (i > nwidth) ? i : nwidth; |
||
1322 |
} |
||
1323 |
/* |
||
1324 |
* we will print an index of the form |
||
1325 |
* %d) |
||
1326 |
* in front of each entry |
||
1327 |
* get the max width of this |
||
1328 |
*/ |
||
1329 |
for (i = n, dwidth = 1; i >= 10; i /= 10) |
||
1330 |
dwidth++; |
||
1331 |
|||
1332 |
smi.args = ap; |
||
1333 |
smi.arg_width = nwidth; |
||
1334 |
smi.num_width = dwidth; |
||
1335 |
print_columns(shl_out, n, select_fmt_entry, (void *) &smi, |
||
1336 |
dwidth + nwidth + 2, 1); |
||
1337 |
|||
1338 |
return n; |
||
1339 |
} |
||
1340 |
|||
1341 |
/* XXX: horrible kludge to fit within the framework */ |
||
1342 |
|||
1343 |
static char *plain_fmt_entry(void *arg, int i, char *buf, int buflen); |
||
1344 |
|||
1345 |
static char * |
||
1346 |
plain_fmt_entry(void *arg, int i, char *buf, int buflen) |
||
1347 |
{ |
||
1348 |
shf_snprintf(buf, buflen, "%s", ((char *const *)arg)[i]); |
||
1349 |
return buf; |
||
1350 |
} |
||
1351 |
|||
1352 |
int |
||
1353 |
pr_list(char *const *ap) |
||
1354 |
{ |
||
1355 |
char *const *pp; |
||
1356 |
int nwidth; |
||
1357 |
int i, n; |
||
1358 |
|||
1359 |
for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) { |
||
1360 |
i = strlen(*pp); |
||
1361 |
nwidth = (i > nwidth) ? i : nwidth; |
||
1362 |
} |
||
1363 |
print_columns(shl_out, n, plain_fmt_entry, (void *) ap, nwidth + 1, 0); |
||
1364 |
|||
1365 |
return n; |
||
1366 |
} |
||
1367 |
#endif /* EDIT */ |
||
1368 |
|||
1369 |
/* |
||
1370 |
* [[ ... ]] evaluation routines |
||
1371 |
*/ |
||
1372 |
|||
1373 |
extern const char *const dbtest_tokens[]; |
||
1374 |
extern const char db_close[]; |
||
1375 |
|||
1376 |
/* Test if the current token is a whatever. Accepts the current token if |
||
1377 |
* it is. Returns 0 if it is not, non-zero if it is (in the case of |
||
1378 |
* TM_UNOP and TM_BINOP, the returned value is a Test_op). |
||
1379 |
*/ |
||
1380 |
static int |
||
1381 |
dbteste_isa(Test_env *te, Test_meta meta) |
||
1382 |
{ |
||
1383 |
int ret = 0; |
||
1384 |
int uqword; |
||
1385 |
char *p; |
||
1386 |
|||
1387 |
✓✓ | 185080 |
if (!*te->pos.wp) |
1388 |
39702 |
return meta == TM_END; |
|
1389 |
|||
1390 |
/* unquoted word? */ |
||
1391 |
✓✓ | 159396 |
for (p = *te->pos.wp; *p == CHAR; p += 2) |
1392 |
; |
||
1393 |
52838 |
uqword = *p == EOS; |
|
1394 |
|||
1395 |
✓✓ | 52838 |
if (meta == TM_UNOP || meta == TM_BINOP) { |
1396 |
✓✓ | 26370 |
if (uqword) { |
1397 |
13234 |
char buf[8]; /* longer than the longest operator */ |
|
1398 |
13234 |
char *q = buf; |
|
1399 |
✓✓ | 79404 |
for (p = *te->pos.wp; |
1400 |
66170 |
*p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2) |
|
1401 |
26468 |
*q++ = p[1]; |
|
1402 |
13234 |
*q = '\0'; |
|
1403 |
13234 |
ret = (int) test_isop(te, meta, buf); |
|
1404 |
13234 |
} |
|
1405 |
✗✓ | 26468 |
} else if (meta == TM_END) |
1406 |
ret = 0; |
||
1407 |
else |
||
1408 |
✓✓ | 52936 |
ret = uqword && |
1409 |
196 |
strcmp(*te->pos.wp, dbtest_tokens[(int) meta]) == 0; |
|
1410 |
|||
1411 |
/* Accept the token? */ |
||
1412 |
✓✓ | 52838 |
if (ret) |
1413 |
13234 |
te->pos.wp++; |
|
1414 |
|||
1415 |
52838 |
return ret; |
|
1416 |
92540 |
} |
|
1417 |
|||
1418 |
static const char * |
||
1419 |
dbteste_getopnd(Test_env *te, Test_op op, int do_eval) |
||
1420 |
{ |
||
1421 |
52740 |
char *s = *te->pos.wp; |
|
1422 |
|||
1423 |
✗✓ | 26370 |
if (!s) |
1424 |
return NULL; |
||
1425 |
|||
1426 |
26370 |
te->pos.wp++; |
|
1427 |
|||
1428 |
✗✓ | 26370 |
if (!do_eval) |
1429 |
return null; |
||
1430 |
|||
1431 |
✓✓ | 26370 |
if (op == TO_STEQL || op == TO_STNEQ) |
1432 |
13136 |
s = evalstr(s, DOTILDE | DOPAT); |
|
1433 |
else |
||
1434 |
13234 |
s = evalstr(s, DOTILDE); |
|
1435 |
|||
1436 |
26370 |
return s; |
|
1437 |
26370 |
} |
|
1438 |
|||
1439 |
static int |
||
1440 |
dbteste_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, |
||
1441 |
int do_eval) |
||
1442 |
{ |
||
1443 |
26468 |
return test_eval(te, op, opnd1, opnd2, do_eval); |
|
1444 |
} |
||
1445 |
|||
1446 |
static void |
||
1447 |
dbteste_error(Test_env *te, int offset, const char *msg) |
||
1448 |
{ |
||
1449 |
te->flags |= TEF_ERROR; |
||
1450 |
internal_errorf(0, "dbteste_error: %s (offset %d)", msg, offset); |
||
1451 |
} |
Generated by: GCOVR (Version 3.3) |