limits.c revision 21924
1/*-
2 * Copyright (c) 1997 by
3 * David L. Nugent <davidn@blaze.net.au>
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, is permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice immediately at the beginning of the file, without modification,
11 *    this list of conditions, and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. This work was done expressly for inclusion into FreeBSD.  Other use
16 *    is permitted provided this notation is included.
17 * 4. Absolutely no warranty of function or purpose is made by the authors.
18 * 5. Modifications may be freely made to this file providing the above
19 *    conditions are met.
20 *
21 * Display/change(+runprogram)/eval resource limits.
22 *
23 *	$FreeBSD: head/usr.bin/limits/limits.c 21924 1997-01-21 15:05:15Z davidn $
24 */
25
26#include <err.h>
27#include <stdio.h>
28#include <string.h>
29#include <sys/types.h>
30#include <sys/stat.h>
31#include <sys/param.h>
32#include <stdlib.h>
33#include <unistd.h>
34#include <stdarg.h>
35#include <ctype.h>
36#include <errno.h>
37#include <pwd.h>
38#include <login_cap.h>
39#include <sys/time.h>
40#include <sys/resource.h>
41
42enum
43{
44  SH_NONE,
45  SH_SH,      /* sh */
46  SH_CSH,     /* csh */
47  SH_BASH,    /* gnu bash */
48  SH_TCSH,    /* tcsh */
49  SH_KSH,     /* (pd)ksh */
50  SH_ZSH,     /* zsh */
51  SH_RC,      /* rc or es */
52  SH_NUMBER
53};
54
55
56/* eval emitter for popular shells.
57 * Why aren't there any standards here? Most shells support either
58 * the csh 'limit' or sh 'ulimit' command, but each varies just
59 * enough that they aren't very compatible from one to the other.
60 */
61static struct {
62  const char * name;	    /* Name of shell */
63  const char * inf;	    /* Name used for 'unlimited' resource */
64  const char * cmd;	    /* Intro text */
65  const char * hard;	    /* Hard limit text */
66  const char * soft;	    /* Soft limit text */
67  const char * both;	    /* Hard+Soft limit text */
68  struct {
69    const char * pfx;
70    const char * sfx;
71    int divisor;
72  } lprm[RLIM_NLIMITS];
73} shellparm[] =
74{
75  { "", "infinity", "Resource limits%s%s:\n", "-max", "-cur", "",
76    {
77	    { "  cputime%-4s      %8s", " secs\n",  1    },
78	    { "  filesize%-4s     %8s", " kb\n",    1024 },
79	    { "  datasize%-4s     %8s", " kb\n",    1024 },
80	    { "  stacksize%-4s    %8s", " kb\n",    1024 },
81	    { "  coredumpsize%-4s %8s", " kb\n",    1024 },
82	    { "  memoryuse%-4s    %8s", " kb\n",    1024 },
83	    { "  memorylocked%-4s %8s", " kb\n",    1024 },
84	    { "  maxprocesses%-4s %8s", "\n",       1    },
85	    { "  openfiles%-4s    %8s", "\n",       1    }
86    }
87  },
88  { "sh", "unlimited", "", " -H", " -S", "",
89    {
90	    { "ulimit%s -t %s", ";\n",  1    },
91	    { "ulimit%s -f %s", ";\n",  512  },
92	    { "ulimit%s -d %s", ";\n",  1024 },
93	    { "ulimit%s -s %s", ";\n",  1024 },
94	    { "ulimit%s -c %s", ";\n",  512  },
95	    { "ulimit%s -m %s", ";\n",  1024 },
96	    { "ulimit%s -l %s", ";\n",  1024 },
97	    { "ulimit%s -u %s", ";\n",  1    },
98	    { "ulimit%s -n %s", ";\n",  1    }
99    }
100  },
101  { "csh", "unlimited", "", " -h", "", NULL,
102    {
103	    { "limit%s cputime %s",      ";\n",  1    },
104	    { "limit%s filesize %s",     ";\n",  1024 },
105	    { "limit%s datasize %s",     ";\n",  1024 },
106	    { "limit%s stacksize %s",    ";\n",  1024 },
107	    { "limit%s coredumpsize %s", ";\n",  1024 },
108	    { "limit%s memoryuse %s",    ";\n",  1024 },
109	    { "limit%s memorylocked %s", ";\n",  1024 },
110	    { "limit%s maxproc %s",      ";\n",  1    },
111	    { "limit%s openfiles %s",    ";\n",  1    }
112    }
113  },
114  { "bash", "unlimited", "", " -H", " -S", "",
115    {
116	    { "ulimit%s -t %s", ";\n",  1    },
117	    { "ulimit%s -f %s", ";\n",  1024 },
118	    { "ulimit%s -d %s", ";\n",  1024 },
119	    { "ulimit%s -s %s", ";\n",  1024 },
120	    { "ulimit%s -c %s", ";\n",  1024 },
121	    { "ulimit%s -v %s", ";\n",  1024 },
122	    { "ulimit%s -m %s", ";\n",  1024 },
123	    { "ulimit%s -u %s", ";\n",  1    },
124	    { "ulimit%s -n %s", ";\n",  1    }
125    }
126  },
127  { "tcsh", "unlimited", "", " -h", "", NULL,
128    {
129	    { "limit%s cputime %s",      ";\n",  1    },
130	    { "limit%s filesize %s",     ";\n",  1024 },
131	    { "limit%s datasize %s",     ";\n",  1024 },
132	    { "limit%s stacksize %s",    ";\n",  1024 },
133	    { "limit%s coredumpsize %s", ";\n",  1024 },
134	    { "limit%s memoryuse %s",    ";\n",  1024 },
135	    { "limit%s memorylocked %s", ";\n",  1024 },
136	    { "limit%s maxproc %s",      ";\n",  1    },
137	    { "limit%s descriptors %s",  ";\n",  1    }
138    }
139  },
140  { "ksh|pdksh", "unlimited", "", " -H", " -S", "",
141    {
142	    { "ulimit%s -t %s", ";\n",  1    },
143	    { "ulimit%s -f %s", ";\n",  512  },
144	    { "ulimit%s -d %s", ";\n",  1024 },
145	    { "ulimit%s -s %s", ";\n",  1024 },
146	    { "ulimit%s -c %s", ";\n",  512  },
147	    { "ulimit%s -m %s", ";\n",  1024 },
148	    { "ulimit%s -l %s", ";\n",  1024 },
149	    { "ulimit%s -p %s", ";\n",  1    },
150	    { "ulimit%s -n %s", ";\n",  1    }
151    }
152  },
153  { "zsh", "unlimited", "", " -H", " -S", "",
154    {
155	    { "ulimit%s -t %s", ";\n",  1    },
156	    { "ulimit%s -f %s", ";\n",  512  },
157	    { "ulimit%s -d %s", ";\n",  1024 },
158	    { "ulimit%s -s %s", ";\n",  1024 },
159	    { "ulimit%s -c %s", ";\n",  512  },
160	    { "ulimit%s -m %s", ";\n",  1024 },
161	    { "ulimit%s -l %s", ";\n",  1024 },
162	    { "ulimit%s -u %s", ";\n",  1    },
163	    { "ulimit%s -n %s", ";\n",  1    }
164    }
165  },
166    /* Note: I recommend you don't use rc/es with 'limits'
167     * as this seems to be fairly buggy.  The values below
168     * are set according to the manpage, but, for example,
169     * the string 'unlimited' is  not actually accepted by
170     * the shell.  'es' is also prone to core dumping when
171     * displaying or modifying limits. Hopefully this will
172     * be fixed.               DLN - Wed Jan 15 20:30 1997
173     */
174  { "rc|es", "unlimited", "", " -h", "", NULL,
175    {
176	    { "limit%s cputime %s",      ";\n",  1    },
177	    { "limit%s filesize %s",     ";\n",  1024 },
178	    { "limit%s datasize %s",     ";\n",  1024 },
179	    { "limit%s stacksize %s",    ";\n",  1024 },
180	    { "limit%s coredumpsize %s", ";\n",  1024 },
181	    { "limit%s memoryuse %s",    ";\n",  1024 },
182	    { "limit%s lockedmemory %s", ";\n",  1024 },
183	    { "limit%s processes %s",    ";\n",  1    },
184	    { "limit%s descriptors %s",  ";\n",  1    }
185    }
186  },
187  { NULL }
188};
189
190static struct {
191  const char * cap;
192  rlim_t (*func)(login_cap_t *, const char *, rlim_t, rlim_t);
193} resources[RLIM_NLIMITS] = {
194  { "cputime",	    login_getcaptime },
195  { "filesize",     login_getcapsize },
196  { "datasize",     login_getcapsize },
197  { "stacksize",    login_getcapsize },
198  { "coredumpsize", login_getcapsize },
199  { "memoryuse",    login_getcapsize },
200  { "memorylocked", login_getcapsize },
201  { "maxproc",	    login_getcapnum, },
202  { "openfiles",    login_getcapnum  }
203};
204
205/*
206 * One letter for each resource levels.
207 * NOTE: There is a dependancy on the corresponding
208 * letter index being equal to the resource number.
209 * If sys/resource.h defines are changed, this needs
210 * to be modified accordingly!
211 */
212
213#define RCS_STRING  "tfdscmlun"
214
215static rlim_t resource_num(int which, int ch, const char *str);
216static void usage(const char *msg, ...);
217static int getshelltype(void);
218static void print_limit(rlim_t limit, unsigned divisor, const char * inf, const char * pfx, const char * sfx, const char * which);
219extern char **environ;
220
221static const char rcs_string[] = RCS_STRING;
222
223int
224main(int argc, char *argv[])
225{
226  char *p, *cls = NULL;
227  char *cleanenv[1];
228  struct passwd * pwd = NULL;
229  int rcswhich, shelltype;
230  int i, num_limits = 0;
231  int ch, doeval = 0, doall = 0;
232  login_cap_t * lc = NULL;
233  enum { ANY=0, SOFT=1, HARD=2, BOTH=3, DISPLAYONLY=4 } type = ANY;
234  enum { RCSUNKNOWN=0, RCSSET=1, RCSSEL=2 } todo = RCSUNKNOWN;
235  int which_limits[RLIM_NLIMITS];
236  rlim_t set_limits[RLIM_NLIMITS];
237  struct rlimit limits[RLIM_NLIMITS];
238
239  /* init resource tables */
240  for (i = 0; i < RLIM_NLIMITS; i++)
241  {
242    which_limits[i] = 0; /* Don't set/display any */
243    set_limits[i] = RLIM_INFINITY;
244    /* Get current resource values */
245    getrlimit(i, &limits[i]);
246  }
247
248  optarg = NULL;
249  while ((ch = getopt(argc, argv, ":EeC:U:BSHac:d:f:l:m:n:s:t:u:")) != EOF) {
250    switch(ch) {
251      case 'a':
252      	doall = 1;
253      	break;
254      case 'E':
255      	environ = cleanenv;
256      	cleanenv[0] = NULL;
257      	break;
258      case 'e':
259      	doeval = 1;
260      	break;
261      case 'C':
262      	cls = optarg;
263      	break;
264      case 'U':
265      	if ((pwd = getpwnam(optarg)) == NULL) {
266      	  if (!isdigit(*optarg) || (pwd = getpwuid(atoi(optarg))) == NULL) {
267      	    usage("Invalid user `%s'\n", optarg);
268      	  }
269      	}
270      	break;
271      case 'H':
272      	type = HARD;
273      	break;
274      case 'S':
275      	type = SOFT;
276      	break;
277      case 'B':
278	type = SOFT|HARD;
279	break;
280      default:
281      case ':': /* Without arg */
282      	if ((p = strchr(rcs_string, optopt)) != NULL) {
283      	  int rcswhich = p - rcs_string;
284	  if (optarg && *optarg == '-') { /* 'arg' is actually a switch */
285	    --optind;		/* back one arg, and make arg NULL */
286	    optarg = NULL;
287	  }
288    	  todo = optarg == NULL ? RCSSEL : RCSSET;
289	  if (type == ANY)
290	    type = BOTH;
291      	  which_limits[rcswhich] = optarg ? type : DISPLAYONLY;
292      	  set_limits[rcswhich] = resource_num(rcswhich, optopt, optarg);
293      	  num_limits++;
294      	  break;
295      	}
296      	/* FALLTHRU */
297      case '?':
298      	usage(NULL);
299    }
300    optarg = NULL;
301  }
302
303  /* If user was specified, get class from that */
304  if (pwd != NULL)
305    lc = login_getclass(pwd);
306  else if (cls != NULL) {
307    lc = login_getclassbyname(cls, NULL);
308    if (lc == NULL || strcmp(cls, lc->lc_class) != 0)
309      fprintf(stderr, "login class '%s' non-existent, using %s\n", cls, lc?lc->lc_class:"current settings");
310  }
311
312  /* If we have a login class, update resource table from that */
313  if (lc != NULL) {
314    for (rcswhich = 0; rcswhich < RLIM_NLIMITS; rcswhich++) {
315      char str[40];
316      rlim_t val;
317
318      /* current value overridden by resourcename or resourcename-cur */
319      sprintf(str, "%s-cur", resources[rcswhich].cap);
320      val = resources[rcswhich].func(lc, resources[rcswhich].cap, limits[rcswhich].rlim_cur, limits[rcswhich].rlim_cur);
321      limits[rcswhich].rlim_cur = resources[rcswhich].func(lc, str, val, val);
322      /* maximum value overridden by resourcename or resourcename-max */
323      sprintf(str, "%s-max", resources[rcswhich].cap);
324      val = resources[rcswhich].func(lc, resources[rcswhich].cap, limits[rcswhich].rlim_max, limits[rcswhich].rlim_max);
325      limits[rcswhich].rlim_max = resources[rcswhich].func(lc, str, val, val);
326    }
327  }
328
329  /* now, let's determine what we wish to do with all this */
330
331  argv += optind;
332
333  /* If we're setting limits or doing an eval (ie. we're not just
334   * displaying), then check that hard limits are not lower than
335   * soft limits, and force rasing the hard limit if we need to if
336   * we are raising the soft limit, or lower the soft limit if we
337   * are lowering the hard limit.
338   */
339  if ((*argv || doeval) && getuid() == 0) {
340    for (rcswhich = 0; rcswhich < RLIM_NLIMITS; rcswhich++) {
341      if (limits[rcswhich].rlim_max != RLIM_INFINITY) {
342	if (limits[rcswhich].rlim_cur == RLIM_INFINITY) {
343	  limits[rcswhich].rlim_max = RLIM_INFINITY;
344	    which_limits[rcswhich] |= HARD;
345	} else if (limits[rcswhich].rlim_cur > limits[rcswhich].rlim_max) {
346	  if (which_limits[rcswhich] == SOFT) {
347	    limits[rcswhich].rlim_max = limits[rcswhich].rlim_cur;
348	    which_limits[rcswhich] |= HARD;
349	  }  else if (which_limits[rcswhich] == HARD) {
350	    limits[rcswhich].rlim_cur = limits[rcswhich].rlim_max;
351	    which_limits[rcswhich] |= SOFT;
352	  } else {
353	    /* else.. if we're specifically setting both to
354	     * silly values, then let it error out.
355	     */
356	  }
357	}
358      }
359    }
360  }
361
362  /* See if we've overridden anything specific on the command line */
363  if (num_limits && todo == RCSSET) {
364    for (rcswhich = 0; rcswhich < RLIM_NLIMITS; rcswhich++) {
365      if (which_limits[rcswhich] & HARD)
366	limits[rcswhich].rlim_max = set_limits[rcswhich];
367      if (which_limits[rcswhich] & SOFT)
368	limits[rcswhich].rlim_cur = set_limits[rcswhich];
369    }
370  }
371
372  /* If *argv is not NULL, then we are being asked to
373   * (perhaps) set environment variables and run a program
374   */
375  if (*argv) {
376    if (doeval)
377      usage("-e cannot be used with `cmd' option\n");
378
379    login_close(lc);
380
381    /* set leading environment variables, like eval(1) */
382    while (*argv && (p = strchr(*argv, '=')))
383      (void)setenv(*argv++, ++p, 1);
384
385    /* Set limits */
386    for (rcswhich = 0; rcswhich < RLIM_NLIMITS; rcswhich++) {
387      if (doall || num_limits == 0 || which_limits[rcswhich] != 0)
388	if (setrlimit(rcswhich, &limits[rcswhich]) == -1)
389	  err(1, "setrlimit %s", resources[rcswhich].cap);
390    }
391
392    if (*argv == NULL)
393      usage(NULL);
394
395    execvp(*argv, argv);
396    err(1, "%s", *argv);
397  }
398
399  shelltype = doeval ? getshelltype() : SH_NONE;
400
401  if (type == ANY) /* Default to soft limits */
402    type = SOFT;
403
404  /* Display limits */
405  printf(shellparm[shelltype].cmd, lc ? " for class " : " (current)", lc ? lc->lc_class : "");
406  for (rcswhich = 0; rcswhich < RLIM_NLIMITS; rcswhich++) {
407    if (doall || num_limits == 0 || which_limits[rcswhich] != 0) {
408      if (which_limits[rcswhich] == ANY || which_limits[rcswhich])
409	which_limits[rcswhich] = type;
410      if (shellparm[shelltype].lprm[rcswhich].pfx) {
411	if (shellparm[shelltype].both && limits[rcswhich].rlim_cur == limits[rcswhich].rlim_max) {
412	  print_limit(limits[rcswhich].rlim_max,
413		      shellparm[shelltype].lprm[rcswhich].divisor,
414		      shellparm[shelltype].inf,
415		      shellparm[shelltype].lprm[rcswhich].pfx,
416		      shellparm[shelltype].lprm[rcswhich].sfx,
417		      shellparm[shelltype].both);
418	} else {
419	  if (which_limits[rcswhich] & HARD) {
420	    print_limit(limits[rcswhich].rlim_max,
421		      shellparm[shelltype].lprm[rcswhich].divisor,
422		      shellparm[shelltype].inf,
423		      shellparm[shelltype].lprm[rcswhich].pfx,
424		      shellparm[shelltype].lprm[rcswhich].sfx,
425		      shellparm[shelltype].hard);
426	  }
427	  if (which_limits[rcswhich] & SOFT) {
428	    print_limit(limits[rcswhich].rlim_cur,
429		      shellparm[shelltype].lprm[rcswhich].divisor,
430		      shellparm[shelltype].inf,
431		      shellparm[shelltype].lprm[rcswhich].pfx,
432		      shellparm[shelltype].lprm[rcswhich].sfx,
433		      shellparm[shelltype].soft);
434	  }
435	}
436      }
437    }
438  }
439
440  login_close(lc);
441  exit(EXIT_SUCCESS);
442}
443
444
445static void
446usage(char const *msg, ...)
447{
448  if (msg) {
449    va_list argp;
450    va_start(argp, msg);
451    vfprintf(stderr, msg, argp);
452    va_end(argp);
453  }
454  (void)fprintf(stderr, "limits [-C class|-U user] [-eaSHBE] [-cdflmnstu [val]] [[name=val ...] cmd]\n");
455  exit(EXIT_FAILURE);
456}
457
458static void
459print_limit(rlim_t limit, unsigned divisor, const char * inf, const char * pfx, const char * sfx, const char * which)
460{
461  char numbr[64];
462  if (limit == RLIM_INFINITY)
463    strcpy(numbr, inf);
464  else
465#ifdef RLIM_LONG
466    sprintf(numbr, "%ql", (long)((limit + divisor/2) / divisor));
467#else
468    sprintf(numbr, "%qd", (quad_t)((limit + divisor/2) / divisor));
469#endif
470  printf(pfx, which, numbr);
471  printf(sfx, which);
472
473}
474
475
476#ifdef RLIM_LONG
477# define STRTOV strtol
478#else
479# define STRTOV strtoq
480#endif
481
482static rlim_t
483resource_num(int which, int ch, const char *str)
484{
485  rlim_t res = RLIM_INFINITY;
486
487  if (str != NULL && !(strcasecmp(str, "inf")==0 || strcasecmp(str, "infinity")==0 || strcasecmp(str, "unlimited")==0)) {
488    const char * s = str;
489    char *e;
490    switch (which)
491    {
492      case RLIMIT_CPU:	/* time values */
493      	errno = 0;
494      	res = 0;
495      	while (*s) {
496      	  rlim_t tim = STRTOV(s, &e, 0);
497      	  if (e == NULL || e == s || errno)
498      	    break;
499      	  switch (*e++) {
500      	    case 0:		   	/* end of string */
501      	      e--;
502      	    default:
503              case 's': case 'S':	/* seconds */
504      	      break;
505            case 'm': case 'M':	/* minutes */
506      	      tim *= 60L;
507      	      break;
508            case 'h': case 'H':	/* hours */
509      	      tim *= (60L * 60L);
510      	      break;
511            case 'd': case 'D':	/* days */
512      	      tim *= (60L * 60L * 24L);
513      	      break;
514            case 'w': case 'W':	/* weeks */
515      	      tim *= (60L * 60L * 24L * 7L);
516            case 'y': case 'Y':	/* Years */
517      	      tim *= (60L * 60L * 24L * 365L);
518	  }
519      	  s = e;
520      	  res += tim;
521      	}
522      	break;
523      case RLIMIT_FSIZE: /* Size values */
524      case RLIMIT_DATA:
525      case RLIMIT_STACK:
526      case RLIMIT_CORE:
527      case RLIMIT_RSS:
528      case RLIMIT_MEMLOCK:
529      	errno = 0;
530      	res = 0;
531      	while (*s) {
532      	  rlim_t mult, tim = STRTOV(s, &e, 0);
533      	  if (e == NULL || e == s || errno)
534      	    break;
535      	  switch (*e++) {
536            case 0:	/* end of string */
537      	      e--;
538      	    default:
539      	      mult = 1;
540      	      break;
541            case 'b': case 'B':	/* 512-byte blocks */
542      	      mult = 512;
543      	      break;
544            case 'k': case 'K':	/* 1024-byte Kilobytes */
545      	      mult = 1024;
546      	      break;
547            case 'm': case 'M':	/* 1024-k kbytes */
548      	      mult = 1024 * 1024;
549      	      break;
550            case 'g': case 'G':	/* 1Gbyte */
551      	      mult = 1024 * 1024 * 1024;
552      	      break;
553#ifndef RLIM_LONG
554      	    case 't': case 'T':	/* 1TBte */
555      	      mult = 1024LL * 1024LL * 1024LL * 1024LL;
556      	      break;
557#endif
558      	  }
559      	  s = e;
560      	  res += (tim * mult);
561      	}
562      	break;
563      case RLIMIT_NPROC:
564      case RLIMIT_NOFILE:
565      	res = STRTOV(s, &e, 0);
566      	s = e;
567      	break;
568    }
569    if (*s)
570      usage("invalid value -%c `%s'\n", ch, str);
571  }
572  return res;
573}
574
575
576static int
577getshellbyname(const char * shell)
578{
579  int i;
580  const char * q;
581  const char * p = strrchr(shell, '/');
582
583  p = p ? ++p : shell;
584  for (i = 0; (q = shellparm[i].name) != NULL; i++) {
585    while (*q)
586    {
587      int j = strcspn(q, "|");
588      if (j == 0)
589	break;
590      if (strncmp(p, q, j) == 0)
591	return i;
592      if (*(q += j))
593	++q;
594    }
595  }
596  return SH_SH;
597}
598
599/*
600 * Determine the type of shell our parent process is
601 * This is quite tricky, not 100% reliable and probably
602 * not nearly as thorough as it should be. Basically, this
603 * is a "best guess" only, but hopefully will work in
604 * most cases.
605 */
606
607static int
608getshelltype(void)
609{
610  pid_t ppid = getppid();
611
612  if (ppid != 1) {
613    FILE * fp;
614    struct stat st;
615    char procdir[MAXPATHLEN], buf[128];
616    int l = sprintf(procdir, "/proc/%ld/", (long)ppid);
617    char * shell = getenv("SHELL");
618
619    if (shell != NULL && stat(shell, &st) != -1)
620    {
621      struct stat st1;
622      strcpy(procdir+l, "file");
623      /* $SHELL is actual shell? */
624      if (stat(procdir, &st1) != -1 && memcmp(&st, &st1, sizeof st) == 0)
625	return getshellbyname(shell);
626    }
627    strcpy(procdir+l, "status");
628    if (stat(procdir, &st) == 0 && (fp = fopen(procdir, "r")) != NULL) {
629      char * p = fgets(buf, sizeof buf, fp)==NULL ? NULL : strtok(buf, " \t");
630      fclose(fp);
631      if (p != NULL)
632	return getshellbyname(p);
633    }
634  }
635  return SH_SH;
636}
637
638