xref: /freebsd/usr.bin/limits/limits.c (revision df7f5d4de4592a8948a25ce01e5bddfbb7ce39dc)
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  *	$Id$
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 
42 enum
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  */
61 static 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 
190 static 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 
215 static rlim_t resource_num(int which, int ch, const char *str);
216 static void usage(const char *msg, ...);
217 static int getshelltype(void);
218 static void print_limit(rlim_t limit, unsigned divisor, const char * inf, const char * pfx, const char * sfx, const char * which);
219 extern char **environ;
220 
221 static const char rcs_string[] = RCS_STRING;
222 
223 int
224 main(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 
445 static void
446 usage(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 
458 static void
459 print_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 
482 static rlim_t
483 resource_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 
576 static int
577 getshellbyname(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 
607 static int
608 getshelltype(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