Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
227 dpurdie 1
/*
2
 * This replace for the standard WATCOM supplied argv processing
3
 * function __Init_Argv.
4
 *
5
 *  Handles $<env-name>... expands into the named environment variabe
6
 *                         accepts $(<env-name>) or ${<env-name>} to
7
 *                         allow for trailing characters which would
8
 *                         normally be a part of the name. The value
9
 *                         of <env-name> is substituted into the string.
10
 *
11
 *  Handles @cmd-file-name read arguments.. any number per line, any
12
 *                         number of lines from <cmd-file-name>.. lines
13
 *                         read from the file get glob'd too!
14
 *
15
 *
16
 *  Handles "" strings     $<env-name> still expanded but results
17
 *                         in a single argument.
18
 *
19
 *  Handles '' strings     nothing inside expanded & results in a
20
 *                         single argument.
21
 *
22
 *  Handles `` expansion   where the command inside the backquotes
23
 *                         gets executed and its output substituted
24
 *                         in place of the string.
25
 *
26
 *  Handles unix like wildcards.. including [] character sets and
27
 *  {} type alternations.
28
 *
29
 */
30
 
31
#include <stdio.h>
32
#include <strings.h>
33
#include <ctype.h>
34
#include <sys/stat.h>
35
#include <sys/types.h>
36
#include <fcntl.h>
37
#include <dirent.h>
38
#include <malloc.h>
39
 
40
#include "glob.h"         /* filename globbing */
41
 
42
 
43
 
44
/*
45
 * Increments for growing various arrays.
46
 */
47
#define ARGV_GROW_SIZE 100
48
#define ARG_GROW_SIZE  2000
49
 
50
/*
51
 * The following are part of the Watcom runtime
52
 */
53
extern char **___Argv;		/* the eventual argv */
54
extern int  ___Argc;		/* the eventual argc */
55
extern char *_LpPgmName;	/* the program name */
56
extern char *_LpCmdLine;	/* the raw cmdline */
57
extern char **environ;		/* the environment strings */
58
 
59
/*
60
 * How big the array pointed to by __Argv really is.
61
 */
62
static int argv_allocated = 0;
63
 
64
/*
65
 * Non-zero if any wildcards were seen processing the arguments.
66
 */
67
static int wildcards_seen = 0;
68
 
69
/*
70
 * Hmmm, should be in a header someplace... maybe... perhaps.
71
 */
72
extern FILE *popen();
73
extern void pclose();
74
 
75
/*
76
 * Internal functions.
77
 */
78
static void glob_cmd_line(char *);
79
static void glob_env(int);
80
static void expand(char*, char*, int, int);
81
static char **exec_prog(char*m, int);
82
static char **read_cmd_file(char*);
83
static int  whitespace_p(char *s, int quote);
84
static char *xgetenv(char *);
85
static void add_arg(char*);
86
static glob_t *wildcard_expand(char*, int);
87
 
88
/*
89
 * Flag given to expand().
90
 */
91
#define ENV_ARG     1
92
#define CMDLINE_ARG 0
93
 
94
/*
95
 * True if `c' is a space, tab.
96
 */
97
#define WS_P(c) ((c) == ' ' || (c) == '\t')
98
 
99
/*
100
 * True if `c' is a CR or LF.
101
 */
102
#define NL_P(c) ((c) == '\r' || (c) == '\n')
103
 
104
/*
105
 * True if `c' is special to filename expansion.
106
 */
107
#define SPECIAL(c) ((c) == '*' || (c) == '?' || (c) == '[' || (c) == '{')
108
 
109
 
110
/******************************************************************************
111
 *
112
 * Primary entry point. By linking in this code we simply replace the standard
113
 * WATCOM supplied version of command line handling: the function __Init_Argv.
114
 *
115
 *****************************************************************************/
116
 
117
void __Init_Argv(void)
118
{
119
  int  nenv_args = 0;
120
  char *tmp;
121
 
122
  extern void __setenvp(void);	/* A WATCOM thing which sets up environ */
123
  __setenvp();                  /* found by grovelling                  */
124
 
125
 
126
#ifdef TEST
127
    printf("_LpCmdLine: %x\n", _LpCmdLine);
128
    printf("_LpPgmName: %x\n", _LpPgmName);
129
    printf("_LpCmdLine: \"%s\"\n", _LpCmdLine);
130
    printf("_LpPgmName: \"%s\"\n", _LpPgmName);
131
#endif
132
  ___Argc = 0;
133
  strlwr(_LpPgmName);
134
 
135
  for (tmp = _LpPgmName; *tmp; tmp++)
136
    {
137
      if (*tmp == '\\')
138
	*tmp = '/';
139
    }
140
  add_arg(_LpPgmName);
141
 
142
  if ((tmp = xgetenv("_argc")) != 0)
143
    {
144
#ifdef TEST
145
      printf("getenv(_argc): %x, \"%s\"\n", tmp, tmp);
146
#endif
147
      nenv_args = atoi(tmp);
148
    }
149
 
150
  /*
151
   *  Stuff from the environment goes first.
152
   */
153
  if (nenv_args)
154
    {
155
      glob_env(nenv_args);
156
    }
157
 
158
  /*
159
   * Tack anything from the command line on the end of the
160
   * argument array.
161
   */
162
  glob_cmd_line(_LpCmdLine);
163
 
164
  ___Argv[___Argc] = 0;
165
 
166
  if (wildcards_seen && ___Argc == 1)
167
    {
168
      printf("No match.\n");
169
      exit(1);
170
    }
171
 
172
#ifdef TEST
173
  printf("final argc: %d\n", ___Argc);
174
#endif
175
}
176
 
177
 
178
/******************************************************************************
179
 *
180
 * Loop through the environment grabbing arguments from it.
181
 *
182
 *****************************************************************************/
183
 
184
static void glob_env(int nargs)
185
{
186
  int i;
187
  char *arg;
188
  char env_name[80];
189
 
190
  if (nargs > 999999)
191
    {
192
      fprintf(stderr, "glob_env: too many arguments (>= 1000000)\n");
193
      exit(42);
194
    }
195
 
196
  for (i = 1; i < nargs; i++)
197
    {
198
      sprintf(env_name, "_argv%d", i);
199
      if ((arg = xgetenv(env_name)) == 0)
200
	{
201
	  fprintf(stderr, "glob_env: BAD environment: _argc: %d, no _argv%d\n",
202
		  nargs, i);
203
	  exit(42);
204
	}
205
      expand(0, arg, ENV_ARG, 0);
206
    }
207
}
208
 
209
 
210
 
211
/******************************************************************************
212
 *
213
 * Find each distinct argument on the command line, taking note
214
 * of quotes etc. then expand & add each argument to the final
215
 * argument vector. Leaves the quote characters intact (for now).
216
 *
217
 *****************************************************************************/
218
 
219
static void glob_cmd_line(const char *cmd_line)
220
{
221
  char *l   = strdup(cmd_line);
222
  char argc = 0;
223
  char *argv[64];	/* can't get more than this on a DOS command line */
224
  int  i;
225
 
226
  while (*l)
227
    {
228
      /*
229
       * Find the start of the argument.
230
       */
231
      while (*l == ' ' || *l == '\t') l++;
232
 
233
      argv[argc++] = l;
234
 
235
      /*
236
       * Find the end of the argument.
237
       */
238
      for (;*l; l++)
239
	{
240
	  if (*l == '\'' || *l == '\"' || *l == '\`')
241
	    {
242
	      int quote_char = *l++;
243
 
244
	      do
245
		{
246
		  if (!*l)
247
		    {
248
		      fprintf(stderr, "Unmatched %c.\n", quote_char);
249
		      exit(42);
250
		    }
251
		} while (*l++ != quote_char);
252
	    }
253
 
254
	  if (*l == ' ' || *l == '\t')
255
	    {
256
	      *l++ = '\0';
257
	      break;
258
	    }
259
	}
260
    }
261
 
262
  for (i = 0; i < argc; i++)
263
    {
264
      expand(0, argv[i], CMDLINE_ARG, 0);
265
    }
266
}
267
 
268
 
269
 
270
/******************************************************************************
271
 *
272
 * Process an argument...
273
 *
274
 * If PREFIX is non-zero then it contains a partial argument
275
 * which has already been processed... it is simply copied into
276
 * the output buffer. This is used when handling variables and
277
 * backquoting.. backquoting particularly crappy because it
278
 * turns a single argument into many.
279
 *
280
 *****************************************************************************/
281
 
282
static void expand(char *prefix, char *arg, int from_env, int in_string)
283
{
284
  int  prefix_len = (prefix)?strlen(prefix):0;
285
  int  buf_len = prefix_len + ARG_GROW_SIZE;
286
  char *buf = (char*)malloc(buf_len);
287
  char *bufp = buf;
288
  int   in_quote = 0;
289
 
290
#ifdef TEST
291
  printf("expand(prefix: \"%s\", arg: \"%s\")\n",
292
	  prefix?prefix:"(nil)", arg);
293
#endif
294
 
295
#define CHECK_BUF_END(l)					\
296
  {								\
297
    int offset = bufp - buf;    				\
298
    while ((offset + (l)) >= buf_len)				\
299
      {								\
300
	buf = (char*)realloc(buf, buf_len += ARG_GROW_SIZE);	\
301
      }				        			\
302
    bufp = buf + offset;                			\
303
  }
304
 
305
  if (!buf)
306
    {
307
      fprintf(stderr, "expand: Out of memory\n");
308
      exit(42);
309
    }
310
 
311
  if (prefix)
312
    {
313
      strcpy(buf, prefix);
314
      bufp += prefix_len;
315
    }
316
 
317
 
318
  /*
319
   * do stuff..
320
   */
321
  for (;*arg; arg++)
322
    {
323
      switch (*arg)
324
	{
325
	case '\'':
326
	  {
327
	    in_quote = !in_quote;
328
	    arg;
329
	    break;
330
	  }
331
 
332
	case '\"':
333
	  {
334
	    if (!in_quote)
335
	      {
336
		in_string = !in_string;
337
		arg;
338
	      }
339
	    else
340
	      goto copy_it;
341
	    break;
342
	  }
343
 
344
	case '\`':
345
	  {
346
	    if (!in_quote)
347
	      {
348
		char *start = ++arg;
349
		char **lines;
350
 
351
		for (arg++; *arg != '\`'; arg++);
352
		CHECK_BUF_END(arg - start);
353
		strncpy(bufp, start, arg - start);
354
		*(bufp + (arg - start)) = '\0';
355
		lines =  exec_prog(bufp, in_string);
356
		*bufp = '\0';
357
 
358
		if (lines)
359
		  {
360
		    for (;*lines; lines++)
361
		      {
362
			if (lines[1])
363
			  {
364
			    /* more arguments after this one. */
365
			    expand((bufp == buf)?0:buf, *lines, 0, in_string);
366
			    bufp = buf;
367
			  }
368
			else
369
			  {
370
			    char *new_arg;
371
			    /* last argument.. tack the rest of arg onto it. */
372
			    new_arg = (char*)malloc(strlen(*lines) +
373
						    strlen(arg + 1) + 1);
374
			    if (!new_arg)
375
			      {
376
				fprintf(stderr, "expand: Out of memory\n");
377
			      }
378
			    strcpy(new_arg, *lines);
379
			    strcat(new_arg, arg + 1);
380
			    expand((bufp == buf)?0:buf, new_arg, 0, in_string);
381
			    free(new_arg);
382
			  }
383
		      }
384
		  }
385
		return;
386
	      }
387
	    else
388
	      goto copy_it;
389
	    break;
390
	  }
391
 
392
	case '$':
393
	  {
394
	    if (!in_quote)
395
	      {
396
		char *start  = ++arg;
397
		int  bracket = 0;
398
		char var_name[200];
399
		char *new_arg;
400
		char *var_val;
401
 
402
		if (*start == '(')
403
		  {
404
		    bracket = ')';
405
		    start++;
406
		  }
407
		else
408
		  if (*start == '{')
409
		    {
410
		      bracket = '}';
411
		      start++;
412
		    }
413
 
414
		if (bracket)
415
		  {
416
		    while (*arg && *arg != bracket)
417
		      arg++;
418
		    if (!*arg)
419
		      {
420
			fprintf(stderr, "Missing %c.\n", bracket);
421
			exit(42);
422
		      }
423
		    strncpy(var_name, start, arg - start);
424
		    var_name[arg-start] = '\0';
425
		    arg++;
426
		  }
427
		else
428
		  {
429
		    while (*arg && (isalnum(*arg) || *arg == '_'))
430
		      arg++;
431
		    strncpy(var_name, start, arg - start);
432
		    var_name[arg-start] = '\0';
433
		  }
434
 
435
		if (var_val = xgetenv(var_name))
436
		  {
437
		    new_arg = (char *)malloc(strlen(arg) +
438
					     strlen(var_val) + 1);
439
		    if (!new_arg)
440
		      {
441
			fprintf(stderr, "expand: Out of memory (expanding var)\n");
442
			exit(42);
443
		      }
444
		    strcpy(new_arg, var_val);
445
		    strcat(new_arg, arg);
446
		    *bufp = '\0';
447
		    expand((bufp == buf)?0:buf, new_arg, 0, in_string);
448
		    free(new_arg);
449
		  }
450
		else
451
		  {
452
		    fprintf(stderr, "Undefined variable: %s\n", var_name);
453
		    exit(42);
454
		  }
455
		return;
456
	      }
457
	    else
458
	      goto copy_it;
459
	    break;
460
	  }
461
 
462
	default:
463
	  {
464
	  copy_it:
465
	    if (SPECIAL(*arg) && (in_quote || in_string))
466
	      {
467
		CHECK_BUF_END(1);
468
		*bufp++ = '\\';
469
	      }
470
	    CHECK_BUF_END(1);
471
	    *bufp++ = *arg;
472
	    break;
473
	  }
474
	}
475
    }
476
 
477
  CHECK_BUF_END(1);
478
  *bufp = '\0';
479
 
480
  /*
481
   * Check for a reference to a command file..
482
   */
483
  if (*buf == '@')
484
    {
485
      char **lines = read_cmd_file(buf + 1);
486
      while (lines && *lines)
487
	{
488
	  expand(0, *lines++, 0, 0);
489
	}
490
      free(buf);
491
      return;
492
    }
493
 
494
  /*
495
   * wildcard expand the argument... arguments from the environment
496
   * only get wildcarded if they don't contain any spaces.
497
   */
498
  if (*buf)
499
    {
500
      if (from_env && (strchr(buf, ' ') || strchr(buf, '\t')))
501
	add_arg(buf);
502
      else
503
	{
504
	  glob_t *gb = wildcard_expand(buf, 1);
505
	  int    i;
506
	  if (gb)
507
	    {
508
	      for (i = 0; i < gb->nused; i++)
509
		add_arg(gb->paths[i]);
510
	      arg_free_glob_t(gb);
511
	    }
512
	  else
513
	    add_arg(buf); /* no wildcards in `buf' so just add it */
514
	}
515
    }
516
 
517
  free(buf);
518
}
519
 
520
 
521
 
522
/******************************************************************************
523
 *
524
 * Add an argument to ___Argv.. expanding Argv if required.
525
 *
526
 *****************************************************************************/
527
 
528
static void add_arg(char *arg)
529
{
530
  char *str = strdup(arg);
531
 
532
 
533
#ifdef TEST
534
  printf("add_arg: \"%s\"\n", arg);
535
#endif
536
 
537
  if (!str)
538
    {
539
      fprintf(stderr, "add_arg: Out of memory.\n");
540
      exit(42);
541
    }
542
 
543
  if (___Argc >= argv_allocated)
544
    {
545
      if (!argv_allocated)
546
	{
547
	  argv_allocated = ARGV_GROW_SIZE;
548
	  ___Argv = (char**)malloc(argv_allocated * sizeof(char*));
549
	}
550
      else
551
	{
552
	  argv_allocated += ARGV_GROW_SIZE;
553
	  ___Argv = (char**)realloc(___Argv, argv_allocated * sizeof(char*));
554
	}
555
    }
556
 
557
  if (!___Argv)
558
    {
559
      fprintf(stderr, "add_arg: Out of memory.\n");
560
      exit(42);
561
    }
562
  ___Argv[___Argc++] = str;
563
}
564
 
565
 
566
/*****************************************************************************
567
 *
568
 * Do the work of expanding the wild card characters in a string.
569
 *
570
 *****************************************************************************/
571
 
572
static glob_t *wildcard_expand(char *s, int no_match_ok)
573
{
574
  glob_t *gb = 0;
575
 
576
#ifdef TEST
577
  printf("wildcard_expand: \"%s\"\n", s);
578
#endif
579
 
580
  if (wildcard_p(s, 0))
581
    {
582
      int status;
583
      char *mess;
584
 
585
      wildcards_seen++;
586
 
587
      if ((gb = (glob_t *) malloc(sizeof(glob_t))) == 0)
588
	{
589
	  fprintf(stderr, "argv: Out of memory.\n");
590
	  exit(42);
591
	}
592
      gb->nused = 0;
593
      gb->paths = 0;
594
      gb->nallocated = 0;
595
      status = arg_glob(s, GLOB_FLAGS, gb);
596
 
597
#ifdef TEST
598
      printf("globbing finds %d names\n", gb->nused);
599
#endif
600
 
601
      if (status == E_NOMATCH)
602
	{
603
	  if (no_match_ok)
604
	    status = 0;
605
	  else
606
	    mess = "No match\n";
607
	}
608
      else
609
      if (status == E_NOMEMORY)
610
	mess = "glob: out of memory\n";
611
      else
612
      if (status == E_IMPOSSIBLE)
613
	mess = "glob: can't handle wildcard drive names\n";
614
      else
615
      if (status == E_NODIR)
616
	mess = "glob: failed opening directory\n";
617
 
618
      if (status)
619
	{
620
	  fprintf(stderr, mess);
621
	  exit(42);
622
	}
623
    }
624
  return gb;
625
}
626
 
627
 
628
 
629
 
630
 
631
 
632
 
633
/******************************************************************************
634
 *
635
 * Exec a subprogram and returns it's output as an array of strings.
636
 * If in_string is non-zero only newlines separate fields.
637
 *
638
 *****************************************************************************/
639
 
640
static char **exec_prog(char *cmd_line, int in_string)
641
{
642
  int   buf_len   = ARG_GROW_SIZE;
643
  char *buf       = malloc(ARG_GROW_SIZE); /* the input line buffer */
644
  char *l         = buf;
645
 
646
  int    argc     = 0;
647
  char **argv     = 0;
648
 
649
  FILE  *fp;
650
  int    ch;
651
 
652
#ifdef TEST
653
  printf("exec_prog(\"%s\")\n", cmd_line);
654
#endif
655
 
656
  if ((fp = popen(cmd_line, "r")) == 0)
657
    {
658
      fprintf(stderr, "exec of subcommand failed...\n");
659
      perror("argv-handler");
660
      exit(42);
661
    }
662
 
663
  /*
664
   * Suck in all the output of the exec'd program.
665
   */
666
 
667
  while ((ch = fgetc(fp)) != EOF)
668
    {
669
      if (l >= (buf + buf_len - 1))
670
	{
671
	  int offset = l - buf;
672
	  buf = (char*)realloc(buf, buf_len += ARG_GROW_SIZE);
673
	  l = buf + offset;
674
	}
675
      *l++ = ch;
676
    }
677
  *l++ = '\0';
678
  pclose(fp);
679
 
680
#ifdef TEST
681
  printf("Output from exec'd prog: |%s|\n", buf);
682
#endif
683
 
684
  /*
685
   * Parse it into fields... straight up guess the sizeof argv as
686
   * half the length of the buffer (ie, every second character a
687
   * space).
688
   */
689
  if (0 == (argv = malloc(((l - buf) / 2) * sizeof(char *))))
690
    {
691
      fprintf(stderr, "Out of memory executing backquote.\n");
692
      exit(42);
693
    }
694
 
695
  for (l = buf; *l;)
696
    {
697
      /*
698
       * Kill leading whitespace.
699
       */
700
      while ( NL_P(*l) || (!in_string && WS_P(*l)))
701
	l++;
702
 
703
      if (*l)
704
	{
705
	  argv[argc++] = l;
706
 
707
	  /*
708
	   * Skip to next whitespace or end of string.
709
	   */
710
	  while (*l && !(NL_P(*l) || (!in_string && WS_P(*l))))
711
	    l++;
712
 
713
	  /*
714
	   * Only terminate if not at end of input.
715
	   */
716
	  if (*l)
717
	    *l++ = '\0';
718
	}
719
    }
720
 
721
  /*
722
   * Fix up the size of the returned array.
723
   */
724
  argv = realloc(argv, sizeof(char *) * (argc + 1));
725
  argv[argc] = 0;
726
 
727
  return argv;
728
}
729
 
730
 
731
 
732
 
733
/******************************************************************************
734
 *
735
 * Read a command file.. returns a null terminated array of fields..
736
 *
737
 *****************************************************************************/
738
 
739
static char **read_cmd_file(char *fname)
740
{
741
  struct stat statb;
742
  int    ifile;
743
  char  *buf;
744
  char **argv = 0;
745
  char  *l;
746
  int    r;
747
  int    argc = 0;
748
 
749
#ifdef TEST
750
  printf("read_cmd_file(%s)\n", fname);
751
#endif
752
 
753
  if ((r = stat(fname, &statb)) == 0)
754
    {
755
#ifdef TEST
756
      printf("read_cmd_file: opens: \"%s\"\n", fname);
757
#endif
758
      if ((ifile = open(fname, O_RDONLY | O_BINARY)) >= 0)
759
	{
760
	  if ((buf = (char*)malloc(statb.st_size + 1)) != 0)
761
	    {
762
	      if (statb.st_size == read(ifile, buf, statb.st_size))
763
		{
764
		  buf[statb.st_size] = '\0';
765
		  close(ifile);
766
		}
767
	      else
768
		{
769
		  fprintf(stderr, "read_cmd_file: read failed, size: %d\n",
770
			  statb.st_size);
771
		  exit(42);
772
		}
773
	    }
774
	  else
775
	    {
776
	      fprintf(stderr, "read_cmd_file: Out of memory\n");
777
	      _exit(42);
778
	    }
779
	}
780
      else
781
	{
782
	  perror("read_cmd_file");
783
	  exit(42);
784
	}
785
    }
786
  else
787
    {
788
      fprintf(stderr, "read_cmd_file: r: %d\n", r);
789
      perror("read_cmd_file: stat failed");
790
      exit(42);
791
    }
792
 
793
 
794
  /*
795
   * Construct the vector of fields... use a straight up guess on the
796
   * size of argv... then fix it up afterwards.
797
   */
798
  if (0 == (argv = (char**)malloc((statb.st_size / 2) * sizeof(char*))))
799
    {
800
      fprintf(stderr, "Out of memory reading command file.\n");
801
      exit(42);
802
    }
803
 
804
  for (l = buf; *l;)
805
    {
806
      char *start = 0;
807
      /*
808
       * Find the start of the argument.
809
       */
810
      while (NL_P(*l) || WS_P(*l))
811
	l++;
812
 
813
      if (*l)
814
	start = l;
815
 
816
      /*
817
       * Find the end of the argument.
818
       */
819
      for (;*l;l++)
820
	{
821
	  if (*l == '\'' || *l == '\"' || *l == '\`')
822
	    {
823
	      int quote_char = *l++;
824
 
825
	      do
826
		{
827
		  if (!*l)
828
		    {
829
		      fprintf(stderr, "Unmatched %c. (reading %s)\n",
830
			      quote_char, fname);
831
		      exit(42);
832
		    }
833
		} while (*l++ != quote_char);
834
	    }
835
 
836
 
837
	  if (NL_P(*l) || WS_P(*l))
838
	    {
839
	      *l++ = '\0';
840
	      break;
841
	    }
842
	}
843
      if (start)
844
	argv[argc++] = start;
845
    }
846
 
847
  argv = realloc(argv, sizeof(char *) * (argc + 1));
848
  argv[argc] = 0;
849
 
850
  return argv;
851
}
852
 
853
 
854
 
855
 
856
/******************************************************************************
857
 *
858
 * A replacement for getenv() which IS case sensitive, the standard
859
 * WATCOM one is not.
860
 *
861
 *****************************************************************************/
862
 
863
static char *xgetenv(char *var_name)
864
{
865
  char **envp = environ;
866
  int  var_len = strlen(var_name);
867
 
868
#ifdef TEST
869
  printf("envp: %x, getenv(\"%s\") returns ", environ, var_name);
870
#endif
871
 
872
  while (envp && *envp)
873
    {
874
      char *endp = (*envp) + var_len;
875
 
876
      if (!strncmp(var_name, *envp, var_len))
877
	{
878
	  if (*endp == '=')
879
	    {
880
#ifdef TEST
881
	      printf("%s\n", endp+1);
882
#endif
883
	      return endp + 1;
884
	    }
885
	}
886
      envp++;
887
    }
888
#ifdef TEST
889
  printf("(nil)\n");
890
#endif
891
  return 0;
892
}
893