xref: /openbsd/bin/csh/exec.c (revision 5b133f3f)
1 /*	$OpenBSD: exec.c,v 1.22 2023/03/08 04:43:04 guenther Exp $	*/
2 /*	$NetBSD: exec.c,v 1.9 1996/09/30 20:03:54 christos Exp $	*/
3 
4 /*-
5  * Copyright (c) 1980, 1991, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <sys/types.h>
34 #include <dirent.h>
35 #include <fcntl.h>
36 #include <sys/stat.h>
37 #include <errno.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <limits.h>
42 #include <stdarg.h>
43 
44 #include "csh.h"
45 #include "extern.h"
46 
47 /*
48  * System level search and execute of a command.  We look in each directory
49  * for the specified command name.  If the name contains a '/' then we
50  * execute only the full path name.  If there is no search path then we
51  * execute only full path names.
52  */
53 extern char **environ;
54 
55 /*
56  * As we search for the command we note the first non-trivial error
57  * message for presentation to the user.  This allows us often
58  * to show that a file has the wrong mode/no access when the file
59  * is not in the last component of the search path, so we must
60  * go on after first detecting the error.
61  */
62 static char *exerr;		/* Execution error message */
63 static Char *expath;		/* Path for exerr */
64 
65 /*
66  * Xhash is an array of HSHSIZ bits (HSHSIZ / 8 chars), which are used
67  * to hash execs.  If it is allocated (havhash true), then to tell
68  * whether ``name'' is (possibly) present in the i'th component
69  * of the variable path, you look at the bit in xhash indexed by
70  * hash(hashname("name"), i).  This is setup automatically
71  * after .login is executed, and recomputed whenever ``path'' is
72  * changed.
73  * The two part hash function is designed to let texec() call the
74  * more expensive hashname() only once and the simple hash() several
75  * times (once for each path component checked).
76  * Byte size is assumed to be 8.
77  */
78 #define	HSHSIZ		8192	/* 1k bytes */
79 #define HSHMASK		(HSHSIZ - 1)
80 #define HSHMUL		243
81 static char xhash[HSHSIZ / 8];
82 
83 #define hash(a, b)	(((a) * HSHMUL + (b)) & HSHMASK)
84 #define bit(h, b)	((h)[(b) >> 3] & 1 << ((b) & 7))	/* bit test */
85 #define bis(h, b)	((h)[(b) >> 3] |= 1 << ((b) & 7))	/* bit set */
86 static int hits, misses;
87 
88 /* Dummy search path for just absolute search when no path */
89 static Char *justabs[] = {STRNULL, 0};
90 
91 static void	pexerr(void);
92 static void	texec(Char *, Char **);
93 static int	hashname(Char *);
94 static int	tellmewhat(struct wordent *, Char *, int len);
95 static int	executable(Char *, Char *, bool);
96 static int	iscommand(Char *);
97 
98 
99 void
doexec(Char ** v,struct command * t)100 doexec(Char **v, struct command *t)
101 {
102     Char *dp, **pv, **av, *sav;
103     struct varent *pathv;
104     bool slash;
105     int hashval = 0, hashval1, i;
106     Char   *blk[2];
107     sigset_t sigset;
108 
109     /*
110      * Glob the command name. We will search $path even if this does something,
111      * as in sh but not in csh.  One special case: if there is no PATH, then we
112      * execute only commands which start with '/'.
113      */
114     blk[0] = t->t_dcom[0];
115     blk[1] = 0;
116     gflag = 0, tglob(blk);
117     if (gflag) {
118 	pv = globall(blk);
119 	if (pv == 0) {
120 	    setname(vis_str(blk[0]));
121 	    stderror(ERR_NAME | ERR_NOMATCH);
122 	}
123 	gargv = 0;
124     }
125     else
126 	pv = saveblk(blk);
127 
128     trim(pv);
129 
130     exerr = 0;
131     expath = Strsave(pv[0]);
132     Vexpath = expath;
133 
134     pathv = adrof(STRpath);
135     if (pathv == 0 && expath[0] != '/') {
136 	blkfree(pv);
137 	pexerr();
138     }
139     slash = any(short2str(expath), '/');
140 
141     /*
142      * Glob the argument list, if necessary. Otherwise trim off the quote bits.
143      */
144     gflag = 0;
145     av = &t->t_dcom[1];
146     tglob(av);
147     if (gflag) {
148 	av = globall(av);
149 	if (av == 0) {
150 	    blkfree(pv);
151 	    setname(vis_str(expath));
152 	    stderror(ERR_NAME | ERR_NOMATCH);
153 	}
154 	gargv = 0;
155     }
156     else
157 	av = saveblk(av);
158 
159     blkfree(t->t_dcom);
160     t->t_dcom = blkspl(pv, av);
161     free(pv);
162     free(av);
163     av = t->t_dcom;
164     trim(av);
165 
166     if (*av == NULL || **av == '\0')
167 	pexerr();
168 
169     xechoit(av);		/* Echo command if -x */
170     /*
171      * Since all internal file descriptors are set to close on exec, we don't
172      * need to close them explicitly here.  Just reorient ourselves for error
173      * messages.
174      */
175     SHIN = 0;
176     SHOUT = 1;
177     SHERR = 2;
178     OLDSTD = 0;
179     /*
180      * We must do this AFTER any possible forking (like `foo` in glob) so that
181      * this shell can still do subprocesses.
182      */
183     sigemptyset(&sigset);
184     sigprocmask(SIG_SETMASK, &sigset, NULL);
185     /*
186      * If no path, no words in path, or a / in the filename then restrict the
187      * command search.
188      */
189     if (pathv == 0 || pathv->vec[0] == 0 || slash)
190 	pv = justabs;
191     else
192 	pv = pathv->vec;
193     sav = Strspl(STRslash, *av);/* / command name for postpending */
194     Vsav = sav;
195     if (havhash)
196 	hashval = hashname(*av);
197     i = 0;
198     hits++;
199     do {
200 	/*
201 	 * Try to save time by looking at the hash table for where this command
202 	 * could be.  If we are doing delayed hashing, then we put the names in
203 	 * one at a time, as the user enters them.  This is kinda like Korn
204 	 * Shell's "tracked aliases".
205 	 */
206 	if (!slash && pv[0][0] == '/' && havhash) {
207 	    hashval1 = hash(hashval, i);
208 	    if (!bit(xhash, hashval1))
209 		goto cont;
210 	}
211 	if (pv[0][0] == 0 || eq(pv[0], STRdot))	/* don't make ./xxx */
212 	    texec(*av, av);
213 	else {
214 	    dp = Strspl(*pv, sav);
215 	    Vdp = dp;
216 	    texec(dp, av);
217 	    Vdp = 0;
218 	    free(dp);
219 	}
220 	misses++;
221 cont:
222 	pv++;
223 	i++;
224     } while (*pv);
225     hits--;
226     Vsav = 0;
227     free(sav);
228     pexerr();
229 }
230 
231 static void
pexerr(void)232 pexerr(void)
233 {
234     /* Couldn't find the damn thing */
235     if (expath) {
236 	setname(vis_str(expath));
237 	Vexpath = 0;
238 	free(expath);
239 	expath = 0;
240     }
241     else
242 	setname("");
243     if (exerr)
244 	stderror(ERR_NAME | ERR_STRING, exerr);
245     stderror(ERR_NAME | ERR_COMMAND);
246 }
247 
248 /*
249  * Execute command f, arg list t.
250  * Record error message if not found.
251  * Also do shell scripts here.
252  */
253 static void
texec(Char * sf,Char ** st)254 texec(Char *sf, Char **st)
255 {
256     char **t;
257     char *f;
258     struct varent *v;
259     Char **vp;
260     Char   *lastsh[2];
261     int     fd;
262     unsigned char c;
263     Char   *st0, **ost;
264 
265     /* The order for the conversions is significant */
266     t = short2blk(st);
267     f = short2str(sf);
268     Vt = t;
269     errno = 0;			/* don't use a previous error */
270     (void) execve(f, t, environ);
271     Vt = 0;
272     blkfree((Char **) t);
273     switch (errno) {
274 
275     case ENOEXEC:
276 	/*
277 	 * From: casper@fwi.uva.nl (Casper H.S. Dik) If we could not execute
278 	 * it, don't feed it to the shell if it looks like a binary!
279 	 */
280 	if ((fd = open(f, O_RDONLY)) != -1) {
281 	    if (read(fd, (char *) &c, 1) == 1) {
282 		if (!Isprint(c) && (c != '\n' && c != '\t')) {
283 		    (void) close(fd);
284 		    /*
285 		     * We *know* what ENOEXEC means.
286 		     */
287 		    stderror(ERR_ARCH, f, strerror(errno));
288 		}
289 	    }
290 	    else
291 		c = '#';
292 	    (void) close(fd);
293 	}
294 	/*
295 	 * If there is an alias for shell, then put the words of the alias in
296 	 * front of the argument list replacing the command name. Note no
297 	 * interpretation of the words at this point.
298 	 */
299 	v = adrof1(STRshell, &aliases);
300 	if (v == 0) {
301 	    vp = lastsh;
302 	    vp[0] = adrof(STRshell) ? value(STRshell) : STR_SHELLPATH;
303 	    vp[1] = NULL;
304 	    if (fd != -1 && c != '#')
305 		vp[0] = STR_BSHELL;
306 	}
307 	else
308 	    vp = v->vec;
309 	st0 = st[0];
310 	st[0] = sf;
311 	ost = st;
312 	st = blkspl(vp, st);	/* Splice up the new arglst */
313 	ost[0] = st0;
314 	sf = *st;
315 	/* The order for the conversions is significant */
316 	t = short2blk(st);
317 	f = short2str(sf);
318 	free(st);
319 	Vt = t;
320 	(void) execve(f, t, environ);
321 	Vt = 0;
322 	blkfree((Char **) t);
323 	/* The sky is falling, the sky is falling! */
324 
325     case ENOMEM:
326 	stderror(ERR_SYSTEM, f, strerror(errno));
327 
328     case ENOENT:
329 	break;
330 
331     default:
332 	if (exerr == 0) {
333 	    exerr = strerror(errno);
334 	    if (expath)
335 		free(expath);
336 	    expath = Strsave(sf);
337 	    Vexpath = expath;
338 	}
339     }
340 }
341 
342 void
execash(Char ** t,struct command * kp)343 execash(Char **t, struct command *kp)
344 {
345     int     saveIN, saveOUT, saveDIAG, saveSTD;
346     int     oSHIN;
347     int     oSHOUT;
348     int     oSHERR;
349     int     oOLDSTD;
350     jmp_buf osetexit;
351     int	    my_reenter;
352     int     odidfds;
353     sig_t   osigint, osigquit, osigterm;
354 
355     if (chkstop == 0 && setintr)
356 	panystop(0);
357     /*
358      * Hmm, we don't really want to do that now because we might
359      * fail, but what is the choice
360      */
361     rechist();
362 
363     osigint  = signal(SIGINT, parintr);
364     osigquit = signal(SIGQUIT, parintr);
365     osigterm = signal(SIGTERM, parterm);
366 
367     odidfds = didfds;
368     oSHIN = SHIN;
369     oSHOUT = SHOUT;
370     oSHERR = SHERR;
371     oOLDSTD = OLDSTD;
372 
373     saveIN = dcopy(SHIN, -1);
374     saveOUT = dcopy(SHOUT, -1);
375     saveDIAG = dcopy(SHERR, -1);
376     saveSTD = dcopy(OLDSTD, -1);
377 
378     lshift(kp->t_dcom, 1);
379 
380     getexit(osetexit);
381 
382     if ((my_reenter = setexit()) == 0) {
383 	SHIN = dcopy(0, -1);
384 	SHOUT = dcopy(1, -1);
385 	SHERR = dcopy(2, -1);
386 	didfds = 0;
387 	doexec(t, kp);
388     }
389 
390     (void) signal(SIGINT, osigint);
391     (void) signal(SIGQUIT, osigquit);
392     (void) signal(SIGTERM, osigterm);
393 
394     doneinp = 0;
395     didfds = odidfds;
396     (void) close(SHIN);
397     (void) close(SHOUT);
398     (void) close(SHERR);
399     (void) close(OLDSTD);
400     SHIN = dmove(saveIN, oSHIN);
401     SHOUT = dmove(saveOUT, oSHOUT);
402     SHERR = dmove(saveDIAG, oSHERR);
403     OLDSTD = dmove(saveSTD, oOLDSTD);
404 
405     resexit(osetexit);
406     if (my_reenter)
407 	stderror(ERR_SILENT);
408 }
409 
410 void
xechoit(Char ** t)411 xechoit(Char **t)
412 {
413     if (adrof(STRecho)) {
414 	(void) fflush(csherr);
415 	blkpr(csherr, t);
416 	(void) fputc('\n', csherr);
417     }
418 }
419 
420 void
dohash(Char ** v,struct command * t)421 dohash(Char **v, struct command *t)
422 {
423     DIR    *dirp;
424     struct dirent *dp;
425     int cnt;
426     int     i = 0;
427     struct varent *pathv = adrof(STRpath);
428     Char  **pv;
429     int     hashval;
430 
431     havhash = 1;
432     for (cnt = 0; cnt < sizeof xhash; cnt++)
433 	xhash[cnt] = 0;
434     if (pathv == 0)
435 	return;
436     for (pv = pathv->vec; *pv; pv++, i++) {
437 	if (pv[0][0] != '/')
438 	    continue;
439 	dirp = opendir(short2str(*pv));
440 	if (dirp == NULL)
441 	    continue;
442 	while ((dp = readdir(dirp)) != NULL) {
443 	    if (dp->d_ino == 0)
444 		continue;
445 	    if (dp->d_name[0] == '.' &&
446 		(dp->d_name[1] == '\0' ||
447 		 (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
448 		continue;
449 	    hashval = hash(hashname(str2short(dp->d_name)), i);
450 	    bis(xhash, hashval);
451 	    /* tw_add_comm_name (dp->d_name); */
452 	}
453 	(void) closedir(dirp);
454     }
455 }
456 
457 void
dounhash(Char ** v,struct command * t)458 dounhash(Char **v, struct command *t)
459 {
460     havhash = 0;
461 }
462 
463 void
hashstat(Char ** v,struct command * t)464 hashstat(Char **v, struct command *t)
465 {
466     if (hits + misses)
467 	(void) fprintf(cshout, "%d hits, %d misses, %d%%\n",
468 		       hits, misses, 100 * hits / (hits + misses));
469 }
470 
471 /*
472  * Hash a command name.
473  */
474 static int
hashname(Char * cp)475 hashname(Char *cp)
476 {
477     long h = 0;
478 
479     while (*cp)
480 	h = hash(h, *cp++);
481     return ((int) h);
482 }
483 
484 static int
iscommand(Char * name)485 iscommand(Char *name)
486 {
487     Char **pv;
488     Char *sav;
489     struct varent *v;
490     bool slash = any(short2str(name), '/');
491     int hashval = 0, hashval1, i;
492 
493     v = adrof(STRpath);
494     if (v == 0 || v->vec[0] == 0 || slash)
495 	pv = justabs;
496     else
497 	pv = v->vec;
498     sav = Strspl(STRslash, name);	/* / command name for postpending */
499     if (havhash)
500 	hashval = hashname(name);
501     i = 0;
502     do {
503 	if (!slash && pv[0][0] == '/' && havhash) {
504 	    hashval1 = hash(hashval, i);
505 	    if (!bit(xhash, hashval1))
506 		goto cont;
507 	}
508 	if (pv[0][0] == 0 || eq(pv[0], STRdot)) {	/* don't make ./xxx */
509 	    if (executable(NULL, name, 0)) {
510 		free(sav);
511 		return i + 1;
512 	    }
513 	}
514 	else {
515 	    if (executable(*pv, sav, 0)) {
516 		free(sav);
517 		return i + 1;
518 	    }
519 	}
520 cont:
521 	pv++;
522 	i++;
523     } while (*pv);
524     free(sav);
525     return 0;
526 }
527 
528 /* Also by:
529  *  Andreas Luik <luik@isaak.isa.de>
530  *  I S A  GmbH - Informationssysteme fuer computerintegrierte Automatisierung
531  *  Azenberstr. 35
532  *  D-7000 Stuttgart 1
533  *  West-Germany
534  * is the executable() routine below and changes to iscommand().
535  * Thanks again!!
536  */
537 
538 /*
539  * executable() examines the pathname obtained by concatenating dir and name
540  * (dir may be NULL), and returns 1 either if it is executable by us, or
541  * if dir_ok is set and the pathname refers to a directory.
542  * This is a bit kludgy, but in the name of optimization...
543  */
544 static int
executable(Char * dir,Char * name,bool dir_ok)545 executable(Char *dir, Char *name, bool dir_ok)
546 {
547     struct stat stbuf;
548     Char    path[PATH_MAX], *dp, *sp;
549     char   *strname;
550 
551     if (dir && *dir) {
552 	for (dp = path, sp = dir; *sp; *dp++ = *sp++)
553 	    if (dp == &path[PATH_MAX]) {
554 		*--dp = '\0';
555 		break;
556 	    }
557 	for (sp = name; *sp; *dp++ = *sp++)
558 	    if (dp == &path[PATH_MAX]) {
559 		*--dp = '\0';
560 		break;
561 	    }
562 	*dp = '\0';
563 	strname = short2str(path);
564     }
565     else
566 	strname = short2str(name);
567     return (stat(strname, &stbuf) != -1 &&
568 	    ((S_ISREG(stbuf.st_mode) &&
569     /* save time by not calling access() in the hopeless case */
570 	      (stbuf.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) &&
571 	      access(strname, X_OK) == 0) ||
572 	     (dir_ok && S_ISDIR(stbuf.st_mode))));
573 }
574 
575 /* The dowhich() is by:
576  *  Andreas Luik <luik@isaak.isa.de>
577  *  I S A  GmbH - Informationssysteme fuer computerintegrierte Automatisierung
578  *  Azenberstr. 35
579  *  D-7000 Stuttgart 1
580  *  West-Germany
581  * Thanks!!
582  */
583 void
dowhich(Char ** v,struct command * c)584 dowhich(Char **v, struct command *c)
585 {
586     struct wordent lex[3];
587     struct varent *vp;
588 
589     lex[0].next = &lex[1];
590     lex[1].next = &lex[2];
591     lex[2].next = &lex[0];
592 
593     lex[0].prev = &lex[2];
594     lex[1].prev = &lex[0];
595     lex[2].prev = &lex[1];
596 
597     lex[0].word = STRNULL;
598     lex[2].word = STRret;
599 
600     while (*++v) {
601 	if ((vp = adrof1(*v, &aliases)) != NULL) {
602 	    (void) fprintf(cshout, "%s: \t aliased to ", vis_str(*v));
603 	    blkpr(cshout, vp->vec);
604 	    (void) fputc('\n', cshout);
605 	    set(STRstatus, Strsave(STR0));
606 	}
607 	else {
608 	    lex[1].word = *v;
609 	    set(STRstatus, Strsave(tellmewhat(lex, NULL, 0) ? STR0 : STR1));
610 	}
611     }
612 }
613 
614 static int
tellmewhat(struct wordent * lexp,Char * str,int len)615 tellmewhat(struct wordent *lexp, Char *str, int len)
616 {
617     int i;
618     struct biltins *bptr;
619     struct wordent *sp = lexp->next;
620     bool    aliased = 0, found;
621     Char   *s0, *s1, *s2, *cmd;
622     Char    qc;
623 
624     if (adrof1(sp->word, &aliases)) {
625 	alias(lexp);
626 	sp = lexp->next;
627 	aliased = 1;
628     }
629 
630     s0 = sp->word;		/* to get the memory freeing right... */
631 
632     /* handle quoted alias hack */
633     if ((*(sp->word) & (QUOTE | TRIM)) == QUOTE)
634 	(sp->word)++;
635 
636     /* do quoting, if it hasn't been done */
637     s1 = s2 = sp->word;
638     while (*s2)
639 	switch (*s2) {
640 	case '\'':
641 	case '"':
642 	    qc = *s2++;
643 	    while (*s2 && *s2 != qc)
644 		*s1++ = *s2++ | QUOTE;
645 	    if (*s2)
646 		s2++;
647 	    break;
648 	case '\\':
649 	    if (*++s2)
650 		*s1++ = *s2++ | QUOTE;
651 	    break;
652 	default:
653 	    *s1++ = *s2++;
654 	}
655     *s1 = '\0';
656 
657     for (bptr = bfunc; bptr < &bfunc[nbfunc]; bptr++) {
658 	if (eq(sp->word, str2short(bptr->bname))) {
659 	    if (str == NULL) {
660 	        if (aliased)
661 		        prlex(cshout, lexp);
662 	        (void) fprintf(cshout, "%s: shell built-in command.\n",
663 			   vis_str(sp->word));
664 	    }
665 	    else
666 		(void) Strlcpy(str, sp->word, len/sizeof(Char));
667 	    sp->word = s0;	/* we save and then restore this */
668 	    return 1;
669 	}
670     }
671 
672     sp->word = cmd = globone(sp->word, G_IGNORE);
673 
674     if ((i = iscommand(sp->word)) != 0) {
675 	Char **pv;
676 	struct varent *v;
677 	bool    slash = any(short2str(sp->word), '/');
678 
679 	v = adrof(STRpath);
680 	if (v == 0 || v->vec[0] == 0 || slash)
681 	    pv = justabs;
682 	else
683 	    pv = v->vec;
684 
685 	while (--i)
686 	    pv++;
687 	if (pv[0][0] == 0 || eq(pv[0], STRdot)) {
688 	    if (!slash) {
689 		sp->word = Strspl(STRdotsl, sp->word);
690 		prlex(cshout, lexp);
691 		free(sp->word);
692 	    }
693 	    else
694 		prlex(cshout, lexp);
695 	}
696 	else {
697 	    s1 = Strspl(*pv, STRslash);
698 	    sp->word = Strspl(s1, sp->word);
699 	    free(s1);
700 	    if (str == NULL)
701 		prlex(cshout, lexp);
702 	    else
703 		(void) Strlcpy(str, sp->word, len/sizeof(Char));
704 	    free(sp->word);
705         }
706 	found = 1;
707     }
708     else {
709 	if (str == NULL) {
710 	    if (aliased)
711 		prlex(cshout, lexp);
712 	    (void) fprintf(csherr,
713 			   "%s: Command not found.\n", vis_str(sp->word));
714 	}
715 	else
716 	    (void) Strlcpy(str, sp->word, len/sizeof(Char));
717 	found = 0;
718     }
719     sp->word = s0;		/* we save and then restore this */
720     free(cmd);
721     return found;
722 }
723