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