master xplshn/aruu / cmd / posix / sh / exec.c
  1/*-
  2 * SPDX-License-Identifier: BSD-3-Clause
  3 *
  4 * Copyright (c) 1991, 1993
  5 *	The Regents of the University of California.  All rights reserved.
  6 *
  7 * This code is derived from software contributed to Berkeley by
  8 * Kenneth Almquist.
  9 *
 10 * Redistribution and use in source and binary forms, with or without
 11 * modification, are permitted provided that the following conditions
 12 * are met:
 13 * 1. Redistributions of source code must retain the above copyright
 14 *    notice, this list of conditions and the following disclaimer.
 15 * 2. Redistributions in binary form must reproduce the above copyright
 16 *    notice, this list of conditions and the following disclaimer in the
 17 *    documentation and/or other materials provided with the distribution.
 18 * 3. Neither the name of the University nor the names of its contributors
 19 *    may be used to endorse or promote products derived from this software
 20 *    without specific prior written permission.
 21 *
 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 32 * SUCH DAMAGE.
 33 */
 34
 35#include <errno.h>
 36#include <fcntl.h>
 37#include <signal.h>
 38#include <stdlib.h>
 39#include <sys/stat.h>
 40#include <sys/types.h>
 41#include <unistd.h>
 42
 43/*
 44 * When commands are first encountered, they are entered in a hash table.
 45 * This ensures that a full path search will not have to be done for them
 46 * on each invocation.
 47 *
 48 * We should investigate converting to a linear search, even though that
 49 * would make the command name "hash" a misnomer.
 50 */
 51
 52#include "../../../shared/paths.h"
 53#include "../../../shared/wexec.h"
 54#include "alias.h"
 55#include "builtins.h"
 56#include "error.h"
 57#include "eval.h"
 58#include "exec.h"
 59#include "input.h"
 60#include "jobs.h"
 61#include "main.h"
 62#include "memalloc.h"
 63#include "mystring.h"
 64#include "nodes.h"
 65#include "options.h"
 66#include "output.h"
 67#include "parser.h"
 68#include "redir.h"
 69#include "shell.h"
 70#include "show.h"
 71#include "syntax.h"
 72#include "var.h"
 73
 74#define CMDTABLESIZE 31 /* should be prime */
 75
 76struct tblentry {
 77  struct tblentry *next;      /* next entry in hash chain */
 78  union param      param;     /* definition of builtin function */
 79  int              special;   /* flag for special builtin commands */
 80  signed char      cmdtype;   /* index identifying command */
 81  char             cmdname[]; /* name of command */
 82};
 83
 84static struct tblentry *cmdtable[CMDTABLESIZE];
 85static int              cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */
 86
 87static void             tryexec(char *, char **, char **);
 88static void             printentry(struct tblentry *, int);
 89static struct tblentry *cmdlookup(const char *, int);
 90static void             delete_cmd_entry(void);
 91static void             addcmdentry(const char *, struct cmdentry *);
 92
 93/*
 94 * Exec a program.  Never returns.  If you change this routine, you may
 95 * have to change the find_command routine as well.
 96 *
 97 * The argv array may be changed and element argv[-1] should be writable.
 98 */
 99
100void
101shellexec(char **argv, char **envp, const char *path, int idx)
102{
103  char       *cmdname;
104  const char *opt;
105  int         e;
106#if FEATURE_NOEXEC
107  struct sigaction sa;
108  int              i;
109  int              ret;
110#endif
111
112#if FEATURE_NOEXEC
113  {
114    /* basename of argv[0] for slash-free lookup */
115    const char   *base = strrchr(argv[0], '/');
116    extern char **environ;
117    base    = base ? base + 1 : argv[0];
118    environ = envp;
119    if (wexec_get_noexec() && wexec_is_builtin(base)) {
120      /* reset caught signal handlers to default for inproc command */
121      for (i = 1; i < NSIG; i++) {
122        if (sigaction(i, NULL, &sa) == 0) {
123          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
124            sa.sa_handler = SIG_DFL;
125            sa.sa_flags   = 0;
126            sigemptyset(&sa.sa_mask);
127            sigaction(i, &sa, NULL);
128          }
129        }
130      }
131
132      fflush(stdout);
133      fflush(stderr);
134      ret = wexec_call_builtin(base, argv);
135      fflush(stdout);
136      fflush(stderr);
137      _exit(ret);
138    }
139  }
140#endif
141
142  if (strchr(argv[0], '/') != NULL) {
143    tryexec(argv[0], argv, envp);
144    e = errno;
145  } else {
146    e = ENOENT;
147    while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) {
148      if (--idx < 0 && opt == NULL) {
149        tryexec(cmdname, argv, envp);
150        if (errno != ENOENT && errno != ENOTDIR)
151          e = errno;
152        if (e == ENOEXEC)
153          break;
154      }
155      stunalloc(cmdname);
156    }
157  }
158
159  /* Map to POSIX errors */
160  if (e == ENOENT || e == ENOTDIR)
161    errorwithstatus(127, "%s: not found", argv[0]);
162  else
163    errorwithstatus(126, "%s: %s", argv[0], strerror(e));
164}
165
166static int
167isbinary(const char *data, size_t len)
168{
169  const char *nul, *p;
170  int         hasletter;
171
172  nul = memchr(data, '\0', len);
173  if (nul == NULL)
174    return 0;
175  /*
176   * POSIX says we shall allow execution if the initial part intended
177   * to be parsed by the shell consists of characters and does not
178   * contain the NUL character. This allows concatenating a shell
179   * script (ending with exec or exit) and a binary payload.
180   *
181   * In order to reject common binary files such as PNG images, check
182   * that there is a lowercase letter or expansion before the last
183   * newline before the NUL character, in addition to the check for
184   * the newline character suggested by POSIX.
185   */
186  hasletter = 0;
187  for (p = data; *p != '\0'; p++) {
188    if ((*p >= 'a' && *p <= 'z') || *p == '$' || *p == '`')
189      hasletter = 1;
190    if (hasletter && *p == '\n')
191      return 0;
192  }
193  return 1;
194}
195
196static void
197tryexec(char *cmd, char **argv, char **envp)
198{
199  int     e, in;
200  ssize_t n;
201  char    buf[256];
202
203  execve(cmd, argv, envp);
204  e = errno;
205  if (e == ENOEXEC) {
206    INTOFF;
207    in = open(cmd, O_RDONLY | O_NONBLOCK);
208    if (in != -1) {
209      n = pread(in, buf, sizeof buf, 0);
210      close(in);
211      if (n > 0 && isbinary(buf, n)) {
212        errno = ENOEXEC;
213        return;
214      }
215    }
216    *argv   = cmd;
217    *--argv = __DECONST(char *, ARUU_PATH_BSHELL);
218    execve(ARUU_PATH_BSHELL, argv, envp);
219  }
220  errno = e;
221}
222
223/*
224 * Do a path search.  The variable path (passed by reference) should be
225 * set to the start of the path before the first call; padvance will update
226 * this value as it proceeds.  Successive calls to padvance will return
227 * the possible path expansions in sequence.  If popt is not NULL, options
228 * are processed: if an option (indicated by a percent sign) appears in
229 * the path entry then *popt will be set to point to it; else *popt will be
230 * set to NULL.  If popt is NULL, percent signs are not special.
231 */
232
233char *
234padvance(const char **path, const char **popt, const char *name)
235{
236  const char *p, *start;
237  char       *q;
238  size_t      len, namelen;
239
240  if (*path == NULL)
241    return NULL;
242  start = *path;
243  if (popt != NULL)
244    for (p = start; *p && *p != ':' && *p != '%'; p++)
245      ; /* nothing */
246  else
247    for (p = start; *p && *p != ':'; p++)
248      ; /* nothing */
249  namelen = strlen(name);
250  len     = p - start + namelen + 2; /* "2" is for '/' and '\0' */
251  STARTSTACKSTR(q);
252  CHECKSTRSPACE(len, q);
253  if (p != start) {
254    memcpy(q, start, p - start);
255    q += p - start;
256    *q++ = '/';
257  }
258  memcpy(q, name, namelen + 1);
259  if (popt != NULL) {
260    if (*p == '%') {
261      *popt = ++p;
262      while (*p && *p != ':')
263        p++;
264    } else
265      *popt = NULL;
266  }
267  if (*p == ':')
268    *path = p + 1;
269  else
270    *path = NULL;
271  return stalloc(len);
272}
273
274/*** Command hashing code ***/
275
276int
277hashcmd(int argc __unused, char **argv __unused)
278{
279  struct tblentry **pp;
280  struct tblentry  *cmdp;
281  int               c;
282  int               verbose;
283  struct cmdentry   entry;
284  char             *name;
285  int               errors;
286
287  errors  = 0;
288  verbose = 0;
289  while ((c = nextopt("rv")) != '\0') {
290    if (c == 'r') {
291      clearcmdentry();
292    } else if (c == 'v') {
293      verbose++;
294    }
295  }
296  if (*argptr == NULL) {
297    for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
298      for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
299        if (cmdp->cmdtype == CMDNORMAL)
300          printentry(cmdp, verbose);
301      }
302    }
303    return 0;
304  }
305  while ((name = *argptr) != NULL) {
306    if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDNORMAL)
307      delete_cmd_entry();
308    find_command(name, &entry, DO_ERR, pathval());
309    if (entry.cmdtype == CMDUNKNOWN)
310      errors = 1;
311    else if (verbose) {
312      cmdp = cmdlookup(name, 0);
313      if (cmdp != NULL)
314        printentry(cmdp, verbose);
315      else {
316        outfmt(out2, "%s: not found\n", name);
317        errors = 1;
318      }
319      flushall();
320    }
321    argptr++;
322  }
323  return errors;
324}
325
326static void
327printentry(struct tblentry *cmdp, int verbose)
328{
329  int         idx;
330  const char *path, *opt;
331  char       *name;
332
333  if (cmdp->cmdtype == CMDNORMAL) {
334    idx  = cmdp->param.index;
335    path = pathval();
336    do {
337      name = padvance(&path, &opt, cmdp->cmdname);
338      stunalloc(name);
339    } while (--idx >= 0);
340    out1str(name);
341  } else if (cmdp->cmdtype == CMDBUILTIN) {
342    out1fmt("builtin %s", cmdp->cmdname);
343  } else if (cmdp->cmdtype == CMDFUNCTION) {
344    out1fmt("function %s", cmdp->cmdname);
345    if (verbose) {
346      INTOFF;
347      name = commandtext(getfuncnode(cmdp->param.func));
348      out1c(' ');
349      out1str(name);
350      ckfree(name);
351      INTON;
352    }
353#if FEATURE_NOEXEC || FEATURE_NOFORK
354  } else if (cmdp->cmdtype == CMDWEXEC) {
355    out1fmt("builtin %s", cmdp->cmdname);
356#endif
357#ifdef DEBUG
358  } else {
359    error("internal error: cmdtype %d", cmdp->cmdtype);
360#endif
361  }
362  out1c('\n');
363}
364
365/*
366 * Resolve a command name.  If you change this routine, you may have to
367 * change the shellexec routine as well.
368 */
369
370void
371find_command(const char *name, struct cmdentry *entry, int act, const char *path)
372{
373  struct tblentry *cmdp, loc_cmd;
374  int              idx;
375  const char      *opt;
376  char            *fullname;
377  struct stat      statb;
378  int              e;
379  int              i;
380  int              spec;
381  int              cd;
382
383  /* If name contains a slash, don't use the hash table */
384  if (strchr(name, '/') != NULL) {
385    entry->cmdtype = CMDNORMAL;
386    entry->u.index = 0;
387    entry->special = 0;
388    return;
389  }
390
391  cd = 0;
392
393  /* If name is in the table, we're done */
394  if ((cmdp = cmdlookup(name, 0)) != NULL) {
395    if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
396      cmdp = NULL;
397    else
398      goto success;
399  }
400
401  /* check for builtin next */
402  if ((i = find_builtin(name, &spec)) >= 0) {
403    INTOFF;
404    cmdp = cmdlookup(name, 1);
405    if (cmdp->cmdtype == CMDFUNCTION)
406      cmdp = &loc_cmd;
407    cmdp->cmdtype     = CMDBUILTIN;
408    cmdp->param.index = i;
409    cmdp->special     = spec;
410    INTON;
411    goto success;
412  }
413
414#if FEATURE_NOEXEC || FEATURE_NOFORK
415  /* when builtin dispatch is active, registered wexec builtins are available
416   * even if they are not present on the filesystem */
417  if ((wexec_get_noexec() || wexec_get_nofork()) && wexec_is_builtin(name)) {
418    INTOFF;
419    cmdp = cmdlookup(name, 1);
420    if (cmdp->cmdtype == CMDFUNCTION)
421      cmdp = &loc_cmd;
422    cmdp->cmdtype     = CMDWEXEC;
423    cmdp->param.index = 0;
424    cmdp->special     = 0;
425    INTON;
426    goto success;
427  }
428#endif
429
430  /* We have to search path. */
431
432  e   = ENOENT;
433  idx = -1;
434  for (; (fullname = padvance(&path, &opt, name)) != NULL; stunalloc(fullname)) {
435    idx++;
436    if (opt) {
437      if (strncmp(opt, "func", 4) == 0) {
438        /* handled below */
439      } else {
440        continue; /* ignore unimplemented options */
441      }
442    }
443    if (fullname[0] != '/')
444      cd = 1;
445    if (stat(fullname, &statb) < 0) {
446      if (errno != ENOENT && errno != ENOTDIR)
447        e = errno;
448      continue;
449    }
450    e = EACCES; /* if we fail, this will be the error */
451    if (!S_ISREG(statb.st_mode))
452      continue;
453    if (opt) { /* this is a %func directory */
454      readcmdfile(fullname, -1 /* verify */);
455      if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
456        error("%s not defined in %s", name, fullname);
457      stunalloc(fullname);
458      goto success;
459    }
460#ifdef notdef
461    if (statb.st_uid == geteuid()) {
462      if ((statb.st_mode & 0100) == 0)
463        goto loop;
464    } else if (statb.st_gid == getegid()) {
465      if ((statb.st_mode & 010) == 0)
466        goto loop;
467    } else {
468      if ((statb.st_mode & 01) == 0)
469        goto loop;
470    }
471#endif
472    TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
473    INTOFF;
474    stunalloc(fullname);
475    cmdp = cmdlookup(name, 1);
476    if (cmdp->cmdtype == CMDFUNCTION)
477      cmdp = &loc_cmd;
478    cmdp->cmdtype     = CMDNORMAL;
479    cmdp->param.index = idx;
480    cmdp->special     = 0;
481    INTON;
482    goto success;
483  }
484
485  if (act & DO_ERR) {
486    if (e == ENOENT || e == ENOTDIR)
487      outfmt(out2, "%s: not found\n", name);
488    else
489      outfmt(out2, "%s: %s\n", name, strerror(e));
490  }
491  entry->cmdtype = CMDUNKNOWN;
492  entry->u.index = 0;
493  entry->special = 0;
494  return;
495
496success:
497  if (cd)
498    cmdtable_cd = 1;
499  entry->cmdtype = cmdp->cmdtype;
500  entry->u       = cmdp->param;
501  entry->special = cmdp->special;
502}
503
504/*
505 * Search the table of builtin commands.
506 */
507
508int
509find_builtin(const char *name, int *special)
510{
511  const unsigned char *bp;
512  size_t               len;
513
514  len = strlen(name);
515  for (bp = builtincmd; *bp; bp += 2 + bp[0]) {
516    if (bp[0] == len && memcmp(bp + 2, name, len) == 0) {
517      *special = (bp[1] & BUILTIN_SPECIAL) != 0;
518      return bp[1] & ~BUILTIN_SPECIAL;
519    }
520  }
521  return -1;
522}
523
524/*
525 * Called when a cd is done.  If any entry in cmdtable depends on the current
526 * directory, simply clear cmdtable completely.
527 */
528
529void
530hashcd(void)
531{
532  if (cmdtable_cd)
533    clearcmdentry();
534}
535
536/*
537 * Called before PATH is changed.  The argument is the new value of PATH;
538 * pathval() still returns the old value at this point.  Called with
539 * interrupts off.
540 */
541
542void
543changepath(const char *newval __unused)
544{
545  clearcmdentry();
546}
547
548/*
549 * Clear out cached utility locations.
550 */
551
552void
553clearcmdentry(void)
554{
555  struct tblentry **tblp;
556  struct tblentry **pp;
557  struct tblentry  *cmdp;
558
559  INTOFF;
560  for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
561    pp = tblp;
562    while ((cmdp = *pp) != NULL) {
563      if (cmdp->cmdtype == CMDNORMAL) {
564        *pp = cmdp->next;
565        ckfree(cmdp);
566      } else {
567        pp = &cmdp->next;
568      }
569    }
570  }
571  cmdtable_cd = 0;
572  INTON;
573}
574
575static unsigned int
576hashname(const char *p)
577{
578  unsigned int hashval;
579
580  hashval = (unsigned char)*p << 4;
581  while (*p)
582    hashval += *p++;
583
584  return (hashval % CMDTABLESIZE);
585}
586
587/*
588 * Locate a command in the command hash table.  If "add" is nonzero,
589 * add the command to the table if it is not already present.  The
590 * variable "lastcmdentry" is set to point to the address of the link
591 * pointing to the entry, so that delete_cmd_entry can delete the
592 * entry.
593 */
594
595static struct tblentry **lastcmdentry;
596
597static struct tblentry *
598cmdlookup(const char *name, int add)
599{
600  struct tblentry  *cmdp;
601  struct tblentry **pp;
602  size_t            len;
603
604  pp = &cmdtable[hashname(name)];
605  for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
606    if (equal(cmdp->cmdname, name))
607      break;
608    pp = &cmdp->next;
609  }
610  if (add && cmdp == NULL) {
611    INTOFF;
612    len  = strlen(name);
613    cmdp = *pp    = ckmalloc(sizeof(struct tblentry) + len + 1);
614    cmdp->next    = NULL;
615    cmdp->cmdtype = CMDUNKNOWN;
616    memcpy(cmdp->cmdname, name, len + 1);
617    INTON;
618  }
619  lastcmdentry = pp;
620  return cmdp;
621}
622
623const void *
624itercmd(const void *entry, struct cmdentry *result)
625{
626  const struct tblentry *e = entry;
627  size_t                 i = 0;
628
629  if (e != NULL) {
630    if (e->next != NULL) {
631      e = e->next;
632      goto success;
633    }
634    i = hashname(e->cmdname) + 1;
635  }
636  for (; i < CMDTABLESIZE; i++)
637    if ((e = cmdtable[i]) != NULL)
638      goto success;
639
640  return (NULL);
641success:
642  result->cmdtype = e->cmdtype;
643  result->cmdname = e->cmdname;
644
645  return (e);
646}
647
648/*
649 * Delete the command entry returned on the last lookup.
650 */
651
652static void
653delete_cmd_entry(void)
654{
655  struct tblentry *cmdp;
656
657  INTOFF;
658  cmdp          = *lastcmdentry;
659  *lastcmdentry = cmdp->next;
660  ckfree(cmdp);
661  INTON;
662}
663
664/*
665 * Add a new command entry, replacing any existing command entry for
666 * the same name.
667 */
668
669static void
670addcmdentry(const char *name, struct cmdentry *entry)
671{
672  struct tblentry *cmdp;
673
674  INTOFF;
675  cmdp = cmdlookup(name, 1);
676  if (cmdp->cmdtype == CMDFUNCTION) {
677    unreffunc(cmdp->param.func);
678  }
679  cmdp->cmdtype = entry->cmdtype;
680  cmdp->param   = entry->u;
681  cmdp->special = entry->special;
682  INTON;
683}
684
685/*
686 * Define a shell function.
687 */
688
689void
690defun(const char *name, union node *func)
691{
692  struct cmdentry entry;
693
694  INTOFF;
695  entry.cmdtype = CMDFUNCTION;
696  entry.u.func  = copyfunc(func);
697  entry.special = 0;
698  addcmdentry(name, &entry);
699  INTON;
700}
701
702/*
703 * Delete a function if it exists.
704 * Called with interrupts off.
705 */
706
707int
708unsetfunc(const char *name)
709{
710  struct tblentry *cmdp;
711
712  if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
713    unreffunc(cmdp->param.func);
714    delete_cmd_entry();
715    return (0);
716  }
717  return (0);
718}
719
720/*
721 * Check if a function by a certain name exists.
722 */
723int
724isfunc(const char *name)
725{
726  struct tblentry *cmdp;
727  cmdp = cmdlookup(name, 0);
728  return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION);
729}
730
731static void
732print_absolute_path(const char *name)
733{
734  const char *pwd;
735
736  if (*name != '/' && (pwd = lookupvar("PWD")) != NULL && *pwd != '\0') {
737    out1str(pwd);
738    if (strcmp(pwd, "/") != 0)
739      outcslow('/', out1);
740  }
741  out1str(name);
742  outcslow('\n', out1);
743}
744
745/*
746 * Shared code for the following builtin commands:
747 *    type, command -v, command -V
748 */
749
750int
751typecmd_impl(int argc, char **argv, int cmd, const char *path)
752{
753  struct cmdentry    entry;
754  struct tblentry   *cmdp;
755  const char *const *pp;
756  struct alias      *ap;
757  int                i;
758  int                error1 = 0;
759
760  if (path != pathval())
761    clearcmdentry();
762
763  for (i = 1; i < argc; i++) {
764    /* First look at the keywords */
765    for (pp = parsekwd; *pp; pp++)
766      if (**pp == *argv[i] && equal(*pp, argv[i]))
767        break;
768
769    if (*pp) {
770      if (cmd == TYPECMD_SMALLV)
771        out1fmt("%s\n", argv[i]);
772      else
773        out1fmt("%s is a shell keyword\n", argv[i]);
774      continue;
775    }
776
777    /* Then look at the aliases */
778    if ((ap = lookupalias(argv[i], 1)) != NULL) {
779      if (cmd == TYPECMD_SMALLV) {
780        out1fmt("alias %s=", argv[i]);
781        out1qstr(ap->val);
782        outcslow('\n', out1);
783      } else
784        out1fmt("%s is an alias for %s\n", argv[i], ap->val);
785      continue;
786    }
787
788    /* Then check if it is a tracked alias */
789    if ((cmdp = cmdlookup(argv[i], 0)) != NULL) {
790      entry.cmdtype = cmdp->cmdtype;
791      entry.u       = cmdp->param;
792      entry.special = cmdp->special;
793    } else {
794      /* Finally use brute force */
795      find_command(argv[i], &entry, 0, path);
796    }
797
798    switch (entry.cmdtype) {
799      case CMDNORMAL: {
800        if (strchr(argv[i], '/') == NULL) {
801          const char *path2 = path;
802          const char *opt2;
803          char       *name;
804          int         j = entry.u.index;
805          do {
806            name = padvance(&path2, &opt2, argv[i]);
807            stunalloc(name);
808          } while (--j >= 0);
809          if (cmd != TYPECMD_SMALLV)
810            out1fmt(
811                "%s is%s ", argv[i], (cmdp && cmd == TYPECMD_TYPE) ? " a tracked alias for" : ""
812            );
813          print_absolute_path(name);
814        } else {
815          if (eaccess(argv[i], X_OK) == 0) {
816            if (cmd != TYPECMD_SMALLV)
817              out1fmt("%s is ", argv[i]);
818            print_absolute_path(argv[i]);
819          } else {
820            if (cmd != TYPECMD_SMALLV)
821              outfmt(out2, "%s: %s\n", argv[i], strerror(errno));
822            error1 |= 127;
823          }
824        }
825        break;
826      }
827      case CMDFUNCTION:
828        if (cmd == TYPECMD_SMALLV)
829          out1fmt("%s\n", argv[i]);
830        else
831          out1fmt("%s is a shell function\n", argv[i]);
832        break;
833
834      case CMDBUILTIN:
835        if (cmd == TYPECMD_SMALLV)
836          out1fmt("%s\n", argv[i]);
837        else if (entry.special)
838          out1fmt("%s is a special shell builtin\n", argv[i]);
839        else
840          out1fmt("%s is a shell builtin\n", argv[i]);
841        break;
842
843#if FEATURE_NOEXEC || FEATURE_NOFORK
844      case CMDWEXEC:
845        if (cmd == TYPECMD_SMALLV) {
846          out1fmt("%s\n", argv[i]);
847        } else {
848          if (wexec_get_nofork() && wexec_is_nofork(argv[i])) {
849            out1fmt(
850                "%s is embedded via wexec and registered by mkbox & genconfig.sh as "
851                "noexec+nofork\n",
852                argv[i]
853            );
854          } else if (wexec_get_noexec()) {
855            out1fmt("%s is embedded via wexec and registered by mkbox as noexec\n", argv[i]);
856          } else {
857            out1fmt("%s is a shell builtin\n", argv[i]);
858          }
859        }
860        break;
861#endif
862
863      default:
864        if (cmd != TYPECMD_SMALLV)
865          outfmt(out2, "%s: not found\n", argv[i]);
866        error1 |= 127;
867        break;
868    }
869  }
870
871  if (path != pathval())
872    clearcmdentry();
873
874  return error1;
875}
876
877/*
878 * Locate and print what a word is...
879 */
880
881int
882typecmd(int argc, char **argv)
883{
884  if (argc > 2 && strcmp(argv[1], "--") == 0)
885    argc--, argv++;
886  return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1));
887}