1 /*
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 1980, 1990, 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 * Robert Elz at The University of Melbourne.
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 /*
36 * Disk quota reporting program.
37 */
38
39 #include <sys/param.h>
40 #include <sys/types.h>
41 #include <sys/file.h>
42 #include <sys/stat.h>
43 #include <sys/mount.h>
44 #include <sys/socket.h>
45
46 #include <rpc/rpc.h>
47 #include <rpc/pmap_prot.h>
48 #include <rpcsvc/rquota.h>
49
50 #include <ufs/ufs/quota.h>
51
52 #include <ctype.h>
53 #include <err.h>
54 #include <fstab.h>
55 #include <grp.h>
56 #include <libutil.h>
57 #include <netdb.h>
58 #include <pwd.h>
59 #include <stdio.h>
60 #include <stdint.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <time.h>
64 #include <unistd.h>
65
66 static const char *qfextension[] = INITQFNAMES;
67
68 struct quotause {
69 struct quotause *next;
70 long flags;
71 struct dqblk dqblk;
72 char fsname[MAXPATHLEN + 1];
73 };
74
75 static char *timeprt(int64_t seconds);
76 static struct quotause *getprivs(long id, int quotatype);
77 static void usage(void) __dead2;
78 static int showuid(u_long uid);
79 static int showgid(u_long gid);
80 static int showusrname(char *name);
81 static int showgrpname(char *name);
82 static int showquotas(int type, u_long id, const char *name);
83 static void showrawquotas(int type, u_long id, struct quotause *qup);
84 static void heading(int type, u_long id, const char *name, const char *tag);
85 static int getufsquota(struct fstab *fs, struct quotause *qup, long id,
86 int quotatype);
87 static int getnfsquota(struct statfs *fst, struct quotause *qup, long id,
88 int quotatype);
89 static enum clnt_stat callaurpc(char *host, int prognum, int versnum, int procnum,
90 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out);
91 static int alldigits(char *s);
92
93 static int hflag;
94 static int lflag;
95 static int rflag;
96 static int qflag;
97 static int vflag;
98 static char *filename = NULL;
99
100 int
main(int argc,char * argv[])101 main(int argc, char *argv[])
102 {
103 int ngroups;
104 gid_t mygid, gidset[NGROUPS];
105 int i, ch, gflag = 0, uflag = 0, errflag = 0;
106
107 while ((ch = getopt(argc, argv, "f:ghlrquv")) != -1) {
108 switch(ch) {
109 case 'f':
110 filename = optarg;
111 break;
112 case 'g':
113 gflag++;
114 break;
115 case 'h':
116 hflag++;
117 break;
118 case 'l':
119 lflag++;
120 break;
121 case 'q':
122 qflag++;
123 break;
124 case 'r':
125 rflag++;
126 break;
127 case 'u':
128 uflag++;
129 break;
130 case 'v':
131 vflag++;
132 break;
133 default:
134 usage();
135 }
136 }
137 argc -= optind;
138 argv += optind;
139 if (!uflag && !gflag)
140 uflag++;
141 if (argc == 0) {
142 if (uflag)
143 errflag += showuid(getuid());
144 if (gflag) {
145 mygid = getgid();
146 ngroups = getgroups(NGROUPS, gidset);
147 if (ngroups < 0)
148 err(1, "getgroups");
149 errflag += showgid(mygid);
150 for (i = 0; i < ngroups; i++)
151 if (gidset[i] != mygid)
152 errflag += showgid(gidset[i]);
153 }
154 return(errflag);
155 }
156 if (uflag && gflag)
157 usage();
158 if (uflag) {
159 for (; argc > 0; argc--, argv++) {
160 if (alldigits(*argv))
161 errflag += showuid(atoi(*argv));
162 else
163 errflag += showusrname(*argv);
164 }
165 return(errflag);
166 }
167 if (gflag) {
168 for (; argc > 0; argc--, argv++) {
169 if (alldigits(*argv))
170 errflag += showgid(atoi(*argv));
171 else
172 errflag += showgrpname(*argv);
173 }
174 }
175 return(errflag);
176 }
177
178 static void
usage(void)179 usage(void)
180 {
181
182 fprintf(stderr, "%s\n%s\n%s\n",
183 "usage: quota [-ghlu] [-f path] [-v | -q | -r]",
184 " quota [-hlu] [-f path] [-v | -q | -r] user ...",
185 " quota -g [-hl] [-f path] [-v | -q | -r] group ...");
186 exit(1);
187 }
188
189 /*
190 * Print out quotas for a specified user identifier.
191 */
192 static int
showuid(u_long uid)193 showuid(u_long uid)
194 {
195 struct passwd *pwd = getpwuid(uid);
196 const char *name;
197
198 if (pwd == NULL)
199 name = "(no account)";
200 else
201 name = pwd->pw_name;
202 return(showquotas(USRQUOTA, uid, name));
203 }
204
205 /*
206 * Print out quotas for a specified user name.
207 */
208 static int
showusrname(char * name)209 showusrname(char *name)
210 {
211 struct passwd *pwd = getpwnam(name);
212
213 if (pwd == NULL) {
214 warnx("%s: unknown user", name);
215 return(1);
216 }
217 return(showquotas(USRQUOTA, pwd->pw_uid, name));
218 }
219
220 /*
221 * Print out quotas for a specified group identifier.
222 */
223 static int
showgid(u_long gid)224 showgid(u_long gid)
225 {
226 struct group *grp = getgrgid(gid);
227 const char *name;
228
229 if (grp == NULL)
230 name = "(no entry)";
231 else
232 name = grp->gr_name;
233 return(showquotas(GRPQUOTA, gid, name));
234 }
235
236 /*
237 * Print out quotas for a specified group name.
238 */
239 static int
showgrpname(char * name)240 showgrpname(char *name)
241 {
242 struct group *grp = getgrnam(name);
243
244 if (grp == NULL) {
245 warnx("%s: unknown group", name);
246 return(1);
247 }
248 return(showquotas(GRPQUOTA, grp->gr_gid, name));
249 }
250
251 static void
prthumanval(int len,u_int64_t bytes)252 prthumanval(int len, u_int64_t bytes)
253 {
254 char buf[len + 1];
255
256 /*
257 * Limit the width to 5 bytes as that is what users expect.
258 */
259 humanize_number(buf, MIN(sizeof(buf), 5), bytes, "",
260 HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL);
261
262 (void)printf(" %*s", len, buf);
263 }
264
265 static int
showquotas(int type,u_long id,const char * name)266 showquotas(int type, u_long id, const char *name)
267 {
268 struct quotause *qup;
269 struct quotause *quplist;
270 const char *msgi, *msgb;
271 const char *nam;
272 char *bgrace = NULL, *igrace = NULL;
273 int lines = 0, overquota = 0;
274 static time_t now;
275
276 if (now == 0)
277 time(&now);
278 quplist = getprivs(id, type);
279 for (qup = quplist; qup; qup = qup->next) {
280 msgi = NULL;
281 if (qup->dqblk.dqb_ihardlimit &&
282 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) {
283 overquota++;
284 msgi = "File limit reached on";
285 }
286 else if (qup->dqblk.dqb_isoftlimit &&
287 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) {
288 overquota++;
289 if (qup->dqblk.dqb_itime > now)
290 msgi = "In file grace period on";
291 else
292 msgi = "Over file quota on";
293 }
294 msgb = NULL;
295 if (qup->dqblk.dqb_bhardlimit &&
296 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) {
297 overquota++;
298 msgb = "Block limit reached on";
299 }
300 else if (qup->dqblk.dqb_bsoftlimit &&
301 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) {
302 overquota++;
303 if (qup->dqblk.dqb_btime > now)
304 msgb = "In block grace period on";
305 else
306 msgb = "Over block quota on";
307 }
308 if (rflag) {
309 showrawquotas(type, id, qup);
310 continue;
311 }
312 if (!vflag &&
313 qup->dqblk.dqb_isoftlimit == 0 &&
314 qup->dqblk.dqb_ihardlimit == 0 &&
315 qup->dqblk.dqb_bsoftlimit == 0 &&
316 qup->dqblk.dqb_bhardlimit == 0)
317 continue;
318 if (qflag) {
319 if ((msgi != NULL || msgb != NULL) &&
320 lines++ == 0)
321 heading(type, id, name, "");
322 if (msgi != NULL)
323 printf("\t%s %s\n", msgi, qup->fsname);
324 if (msgb != NULL)
325 printf("\t%s %s\n", msgb, qup->fsname);
326 continue;
327 }
328 if (!vflag &&
329 qup->dqblk.dqb_curblocks == 0 &&
330 qup->dqblk.dqb_curinodes == 0)
331 continue;
332 if (lines++ == 0)
333 heading(type, id, name, "");
334 nam = qup->fsname;
335 if (strlen(qup->fsname) > 15) {
336 printf("%s\n", qup->fsname);
337 nam = "";
338 }
339 printf("%-15s", nam);
340 if (hflag) {
341 prthumanval(7, dbtob(qup->dqblk.dqb_curblocks));
342 printf("%c", (msgb == NULL) ? ' ' : '*');
343 prthumanval(7, dbtob(qup->dqblk.dqb_bsoftlimit));
344 prthumanval(7, dbtob(qup->dqblk.dqb_bhardlimit));
345 } else {
346 printf(" %7ju%c %7ju %7ju",
347 (uintmax_t)dbtob(qup->dqblk.dqb_curblocks)
348 / 1024,
349 (msgb == NULL) ? ' ' : '*',
350 (uintmax_t)dbtob(qup->dqblk.dqb_bsoftlimit)
351 / 1024,
352 (uintmax_t)dbtob(qup->dqblk.dqb_bhardlimit)
353 / 1024);
354 }
355 if (msgb != NULL)
356 bgrace = timeprt(qup->dqblk.dqb_btime);
357 if (msgi != NULL)
358 igrace = timeprt(qup->dqblk.dqb_itime);
359 printf("%8s %6ju%c %6ju %6ju%8s\n"
360 , (msgb == NULL) ? "" : bgrace
361 , (uintmax_t)qup->dqblk.dqb_curinodes
362 , (msgi == NULL) ? ' ' : '*'
363 , (uintmax_t)qup->dqblk.dqb_isoftlimit
364 , (uintmax_t)qup->dqblk.dqb_ihardlimit
365 , (msgi == NULL) ? "" : igrace
366 );
367 if (msgb != NULL)
368 free(bgrace);
369 if (msgi != NULL)
370 free(igrace);
371 }
372 if (!qflag && !rflag && lines == 0)
373 heading(type, id, name, "none");
374 return (overquota);
375 }
376
377 static void
showrawquotas(int type,u_long id,struct quotause * qup)378 showrawquotas(int type, u_long id, struct quotause *qup)
379 {
380 time_t t;
381
382 printf("Raw %s quota information for id %lu on %s\n",
383 type == USRQUOTA ? "user" : "group", id, qup->fsname);
384 printf("block hard limit: %ju\n",
385 (uintmax_t)qup->dqblk.dqb_bhardlimit);
386 printf("block soft limit: %ju\n",
387 (uintmax_t)qup->dqblk.dqb_bsoftlimit);
388 printf("current block count: %ju\n",
389 (uintmax_t)qup->dqblk.dqb_curblocks);
390 printf("i-node hard limit: %ju\n",
391 (uintmax_t)qup->dqblk.dqb_ihardlimit);
392 printf("i-node soft limit: %ju\n",
393 (uintmax_t)qup->dqblk.dqb_isoftlimit);
394 printf("current i-node count: %ju\n",
395 (uintmax_t)qup->dqblk.dqb_curinodes);
396 printf("block grace time: %jd",
397 (intmax_t)qup->dqblk.dqb_btime);
398 if (qup->dqblk.dqb_btime != 0) {
399 t = qup->dqblk.dqb_btime;
400 printf(" %s", ctime(&t));
401 } else {
402 printf("\n");
403 }
404 printf("i-node grace time: %jd", (intmax_t)qup->dqblk.dqb_itime);
405 if (qup->dqblk.dqb_itime != 0) {
406 t = qup->dqblk.dqb_itime;
407 printf(" %s", ctime(&t));
408 } else {
409 printf("\n");
410 }
411 }
412
413
414 static void
heading(int type,u_long id,const char * name,const char * tag)415 heading(int type, u_long id, const char *name, const char *tag)
416 {
417
418 printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type],
419 name, *qfextension[type], id, tag);
420 if (!qflag && tag[0] == '\0') {
421 printf("%-15s %7s %8s %7s %7s %6s %7s %6s%8s\n"
422 , "Filesystem"
423 , "usage"
424 , "quota"
425 , "limit"
426 , "grace"
427 , "files"
428 , "quota"
429 , "limit"
430 , "grace"
431 );
432 }
433 }
434
435 /*
436 * Calculate the grace period and return a printable string for it.
437 */
438 static char *
timeprt(int64_t seconds)439 timeprt(int64_t seconds)
440 {
441 time_t hours, minutes;
442 char *buf;
443 static time_t now;
444
445 if (now == 0)
446 time(&now);
447 if (now > seconds) {
448 if ((buf = strdup("none")) == NULL)
449 errx(1, "strdup() failed in timeprt()");
450 return (buf);
451 }
452 seconds -= now;
453 minutes = (seconds + 30) / 60;
454 hours = (minutes + 30) / 60;
455 if (hours >= 36) {
456 if (asprintf(&buf, "%lddays", ((long)hours + 12) / 24) < 0)
457 errx(1, "asprintf() failed in timeprt(1)");
458 return (buf);
459 }
460 if (minutes >= 60) {
461 if (asprintf(&buf, "%2ld:%ld", (long)minutes / 60,
462 (long)minutes % 60) < 0)
463 errx(1, "asprintf() failed in timeprt(2)");
464 return (buf);
465 }
466 if (asprintf(&buf, "%2ld", (long)minutes) < 0)
467 errx(1, "asprintf() failed in timeprt(3)");
468 return (buf);
469 }
470
471 /*
472 * Collect the requested quota information.
473 */
474 static struct quotause *
getprivs(long id,int quotatype)475 getprivs(long id, int quotatype)
476 {
477 struct quotause *qup, *quptail = NULL;
478 struct fstab *fs;
479 struct quotause *quphead;
480 struct statfs *fst;
481 int nfst, i;
482 struct statfs sfb;
483
484 qup = quphead = (struct quotause *)0;
485
486 if (filename != NULL && statfs(filename, &sfb) != 0)
487 err(1, "cannot statfs %s", filename);
488 nfst = getmntinfo(&fst, MNT_NOWAIT);
489 if (nfst == 0)
490 errx(2, "no filesystems mounted!");
491 setfsent();
492 for (i = 0; i < nfst; i++) {
493 if (qup == NULL) {
494 if ((qup = (struct quotause *)malloc(sizeof *qup))
495 == NULL)
496 errx(2, "out of memory");
497 }
498 /*
499 * See if the user requested a specific file system
500 * or specified a file inside a mounted file system.
501 */
502 if (filename != NULL &&
503 strcmp(sfb.f_mntonname, fst[i].f_mntonname) != 0)
504 continue;
505 if (strcmp(fst[i].f_fstypename, "nfs") == 0) {
506 if (lflag)
507 continue;
508 if (getnfsquota(&fst[i], qup, id, quotatype) == 0)
509 continue;
510 } else if (strcmp(fst[i].f_fstypename, "ufs") == 0) {
511 /*
512 * XXX
513 * UFS filesystems must be in /etc/fstab, and must
514 * indicate that they have quotas on (?!) This is quite
515 * unlike SunOS where quotas can be enabled/disabled
516 * on a filesystem independent of /etc/fstab, and it
517 * will still print quotas for them.
518 */
519 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
520 continue;
521 if (getufsquota(fs, qup, id, quotatype) == 0)
522 continue;
523 } else
524 continue;
525 strcpy(qup->fsname, fst[i].f_mntonname);
526 if (quphead == NULL)
527 quphead = qup;
528 else
529 quptail->next = qup;
530 quptail = qup;
531 quptail->next = 0;
532 qup = NULL;
533 }
534 if (qup)
535 free(qup);
536 endfsent();
537 return (quphead);
538 }
539
540 /*
541 * Check to see if a particular quota is available.
542 */
543 static int
getufsquota(struct fstab * fs,struct quotause * qup,long id,int quotatype)544 getufsquota(struct fstab *fs, struct quotause *qup, long id, int quotatype)
545 {
546 struct quotafile *qf;
547
548 if ((qf = quota_open(fs, quotatype, O_RDONLY)) == NULL)
549 return (0);
550 if (quota_read(qf, &qup->dqblk, id) != 0)
551 return (0);
552 quota_close(qf);
553 return (1);
554 }
555
556 static int
getnfsquota(struct statfs * fst,struct quotause * qup,long id,int quotatype)557 getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype)
558 {
559 struct ext_getquota_args gq_args;
560 struct getquota_args old_gq_args;
561 struct getquota_rslt gq_rslt;
562 struct dqblk *dqp = &qup->dqblk;
563 struct timeval tv;
564 char *cp, host[NI_MAXHOST];
565 enum clnt_stat call_stat;
566
567 if (fst->f_flags & MNT_LOCAL)
568 return (0);
569
570 /*
571 * must be some form of "hostname:/path"
572 */
573 cp = fst->f_mntfromname;
574 do {
575 cp = strrchr(cp, ':');
576 } while (cp != NULL && *(cp + 1) != '/');
577 if (cp == NULL) {
578 warnx("cannot find hostname for %s", fst->f_mntfromname);
579 return (0);
580 }
581 memset(host, 0, sizeof(host));
582 memcpy(host, fst->f_mntfromname, cp - fst->f_mntfromname);
583 host[sizeof(host) - 1] = '\0';
584
585 /* Avoid attempting the RPC for special amd(8) filesystems. */
586 if (strncmp(fst->f_mntfromname, "pid", 3) == 0 &&
587 strchr(fst->f_mntfromname, '@') != NULL)
588 return (0);
589
590 gq_args.gqa_pathp = cp + 1;
591 gq_args.gqa_id = id;
592 gq_args.gqa_type = quotatype;
593
594 call_stat = callaurpc(host, RQUOTAPROG, EXT_RQUOTAVERS,
595 RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_ext_getquota_args, (char *)&gq_args,
596 (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt);
597 if (call_stat == RPC_PROGVERSMISMATCH || call_stat == RPC_PROGNOTREGISTERED) {
598 if (quotatype == USRQUOTA) {
599 old_gq_args.gqa_pathp = cp + 1;
600 old_gq_args.gqa_uid = id;
601 call_stat = callaurpc(host, RQUOTAPROG, RQUOTAVERS,
602 RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_getquota_args, (char *)&old_gq_args,
603 (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt);
604 } else {
605 /* Old rpc quota does not support group type */
606 return (0);
607 }
608 }
609 if (call_stat != 0)
610 return (call_stat);
611
612 switch (gq_rslt.status) {
613 case Q_NOQUOTA:
614 break;
615 case Q_EPERM:
616 warnx("quota permission error, host: %s",
617 fst->f_mntfromname);
618 break;
619 case Q_OK:
620 gettimeofday(&tv, NULL);
621 /* blocks*/
622 dqp->dqb_bhardlimit =
623 ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
624 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
625 dqp->dqb_bsoftlimit =
626 ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
627 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
628 dqp->dqb_curblocks =
629 ((uint64_t)gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
630 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize) / DEV_BSIZE;
631 /* inodes */
632 dqp->dqb_ihardlimit =
633 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
634 dqp->dqb_isoftlimit =
635 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
636 dqp->dqb_curinodes =
637 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
638 /* grace times */
639 dqp->dqb_btime =
640 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
641 dqp->dqb_itime =
642 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
643 return (1);
644 default:
645 warnx("bad rpc result, host: %s", fst->f_mntfromname);
646 break;
647 }
648
649 return (0);
650 }
651
652 static enum clnt_stat
callaurpc(char * host,int prognum,int versnum,int procnum,xdrproc_t inproc,char * in,xdrproc_t outproc,char * out)653 callaurpc(char *host, int prognum, int versnum, int procnum,
654 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out)
655 {
656 enum clnt_stat clnt_stat;
657 struct timeval timeout, tottimeout;
658
659 CLIENT *client = NULL;
660
661 client = clnt_create(host, prognum, versnum, "udp");
662 if (client == NULL)
663 return ((int)rpc_createerr.cf_stat);
664 timeout.tv_usec = 0;
665 timeout.tv_sec = 6;
666 CLNT_CONTROL(client, CLSET_RETRY_TIMEOUT, (char *)(void *)&timeout);
667
668 client->cl_auth = authunix_create_default();
669 tottimeout.tv_sec = 25;
670 tottimeout.tv_usec = 0;
671 clnt_stat = clnt_call(client, procnum, inproc, in,
672 outproc, out, tottimeout);
673 return (clnt_stat);
674 }
675
676 static int
alldigits(char * s)677 alldigits(char *s)
678 {
679 int c;
680
681 c = *s++;
682 do {
683 if (!isdigit(c))
684 return (0);
685 } while ((c = *s++));
686 return (1);
687 }
688