Annotation of src/usr.bin/quota/quota.c, Revision 1.11
1.11 ! deraadt 1: /* $OpenBSD: quota.c,v 1.10 1998/07/10 08:17:39 deraadt Exp $ */
1.5 deraadt 2:
1.1 deraadt 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. All advertising materials mentioning features or use of this software
19: * must display the following acknowledgement:
20: * This product includes software developed by the University of
21: * California, Berkeley and its contributors.
22: * 4. Neither the name of the University nor the names of its contributors
23: * may be used to endorse or promote products derived from this software
24: * without specific prior written permission.
25: *
26: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36: * SUCH DAMAGE.
37: */
38:
39: #ifndef lint
40: static char copyright[] =
41: "@(#) Copyright (c) 1980, 1990, 1993\n\
42: The Regents of the University of California. All rights reserved.\n";
43: #endif /* not lint */
44:
45: #ifndef lint
46: /*static char sccsid[] = "from: @(#)quota.c 8.1 (Berkeley) 6/6/93";*/
1.11 ! deraadt 47: static char rcsid[] = "$OpenBSD: quota.c,v 1.10 1998/07/10 08:17:39 deraadt Exp $";
1.1 deraadt 48: #endif /* not lint */
49:
50: /*
51: * Disk quota reporting program.
52: */
53: #include <sys/param.h>
54: #include <sys/types.h>
55: #include <sys/file.h>
56: #include <sys/stat.h>
57: #include <sys/mount.h>
58: #include <sys/socket.h>
59: #include <ufs/ufs/quota.h>
60: #include <stdio.h>
61: #include <stdlib.h>
62: #include <fstab.h>
63: #include <ctype.h>
64: #include <string.h>
65: #include <pwd.h>
66: #include <grp.h>
67: #include <errno.h>
68:
69: #include <netdb.h>
70: #include <rpc/rpc.h>
71: #include <rpc/pmap_prot.h>
72: #include <rpcsvc/rquota.h>
73:
74: char *qfname = QUOTAFILENAME;
75: char *qfextension[] = INITQFNAMES;
76:
77: struct quotause {
78: struct quotause *next;
79: long flags;
80: struct dqblk dqblk;
81: char fsname[MAXPATHLEN + 1];
82: };
83: #define FOUND 0x01
84:
85: char *timeprt __P((time_t seconds));
86: struct quotause *getprivs __P((long id, int quotatype));
87:
88: int qflag;
89: int vflag;
90:
91: main(argc, argv)
92: char *argv[];
93: {
94: int ngroups;
95: gid_t mygid, gidset[NGROUPS];
96: int i, gflag = 0, uflag = 0;
1.3 deraadt 97: int ch;
1.1 deraadt 98: extern char *optarg;
99: extern int optind, errno;
100:
1.3 deraadt 101: while ((ch = getopt(argc, argv, "ugvq")) != -1) {
1.1 deraadt 102: switch(ch) {
103: case 'g':
104: gflag++;
105: break;
106: case 'u':
107: uflag++;
108: break;
109: case 'v':
110: vflag++;
111: break;
112: case 'q':
113: qflag++;
114: break;
115: default:
116: usage();
117: }
118: }
119: argc -= optind;
120: argv += optind;
121: if (!uflag && !gflag)
122: uflag++;
123: if (argc == 0) {
124: if (uflag)
125: showuid(getuid());
126: if (gflag) {
127: mygid = getgid();
128: ngroups = getgroups(NGROUPS, gidset);
129: if (ngroups < 0) {
130: perror("quota: getgroups");
131: exit(1);
132: }
133: showgid(mygid);
134: for (i = 0; i < ngroups; i++)
135: if (gidset[i] != mygid)
136: showgid(gidset[i]);
137: }
138: exit(0);
139: }
140: if (uflag && gflag)
141: usage();
142: if (uflag) {
143: for (; argc > 0; argc--, argv++) {
144: if (alldigits(*argv))
145: showuid(atoi(*argv));
146: else
147: showusrname(*argv);
148: }
149: exit(0);
150: }
151: if (gflag) {
152: for (; argc > 0; argc--, argv++) {
153: if (alldigits(*argv))
154: showgid(atoi(*argv));
155: else
156: showgrpname(*argv);
157: }
158: exit(0);
159: }
160: }
161:
162: usage()
163: {
164:
165: fprintf(stderr, "%s\n%s\n%s\n",
166: "Usage: quota [-guqv]",
167: "\tquota [-qv] -u username ...",
168: "\tquota [-qv] -g groupname ...");
169: exit(1);
170: }
171:
172: /*
173: * Print out quotas for a specified user identifier.
174: */
175: showuid(uid)
176: u_long uid;
177: {
178: struct passwd *pwd = getpwuid(uid);
179: u_long myuid;
180: char *name;
181:
182: if (pwd == NULL)
183: name = "(no account)";
184: else
185: name = pwd->pw_name;
186: myuid = getuid();
187: if (uid != myuid && myuid != 0) {
188: printf("quota: %s (uid %d): permission denied\n", name, uid);
189: return;
190: }
191: showquotas(USRQUOTA, uid, name);
192: }
193:
194: /*
195: * Print out quotas for a specifed user name.
196: */
197: showusrname(name)
198: char *name;
199: {
200: struct passwd *pwd = getpwnam(name);
201: u_long myuid;
202:
203: if (pwd == NULL) {
204: fprintf(stderr, "quota: %s: unknown user\n", name);
205: return;
206: }
207: myuid = getuid();
208: if (pwd->pw_uid != myuid && myuid != 0) {
209: fprintf(stderr, "quota: %s (uid %d): permission denied\n",
1.8 millert 210: pwd->pw_name, pwd->pw_uid);
1.1 deraadt 211: return;
212: }
1.8 millert 213: showquotas(USRQUOTA, pwd->pw_uid, pwd->pw_name);
1.1 deraadt 214: }
215:
216: /*
217: * Print out quotas for a specified group identifier.
218: */
219: showgid(gid)
220: u_long gid;
221: {
222: struct group *grp = getgrgid(gid);
223: int ngroups;
224: gid_t mygid, gidset[NGROUPS];
225: register int i;
226: char *name;
227:
228: if (grp == NULL)
229: name = "(no entry)";
230: else
231: name = grp->gr_name;
232: mygid = getgid();
233: ngroups = getgroups(NGROUPS, gidset);
234: if (ngroups < 0) {
235: perror("quota: getgroups");
236: return;
237: }
238: if (gid != mygid) {
239: for (i = 0; i < ngroups; i++)
240: if (gid == gidset[i])
241: break;
242: if (i >= ngroups && getuid() != 0) {
243: fprintf(stderr,
244: "quota: %s (gid %d): permission denied\n",
245: name, gid);
246: return;
247: }
248: }
249: showquotas(GRPQUOTA, gid, name);
250: }
251:
252: /*
253: * Print out quotas for a specifed group name.
254: */
255: showgrpname(name)
256: char *name;
257: {
258: struct group *grp = getgrnam(name);
259: int ngroups;
260: gid_t mygid, gidset[NGROUPS];
261: register int i;
262:
263: if (grp == NULL) {
264: fprintf(stderr, "quota: %s: unknown group\n", name);
265: return;
266: }
267: mygid = getgid();
268: ngroups = getgroups(NGROUPS, gidset);
269: if (ngroups < 0) {
270: perror("quota: getgroups");
271: return;
272: }
273: if (grp->gr_gid != mygid) {
274: for (i = 0; i < ngroups; i++)
275: if (grp->gr_gid == gidset[i])
276: break;
277: if (i >= ngroups && getuid() != 0) {
278: fprintf(stderr,
279: "quota: %s (gid %d): permission denied\n",
1.8 millert 280: grp->gr_name, grp->gr_gid);
1.1 deraadt 281: return;
282: }
283: }
1.8 millert 284: showquotas(GRPQUOTA, grp->gr_gid, grp->gr_name);
1.1 deraadt 285: }
286:
287: showquotas(type, id, name)
288: int type;
289: u_long id;
290: char *name;
291: {
292: register struct quotause *qup;
293: struct quotause *quplist;
294: char *msgi, *msgb, *nam;
295: int myuid, fd, lines = 0;
296: static int first;
297: static time_t now;
298:
299: if (now == 0)
300: time(&now);
301: quplist = getprivs(id, type);
302: for (qup = quplist; qup; qup = qup->next) {
303: if (!vflag &&
304: qup->dqblk.dqb_isoftlimit == 0 &&
305: qup->dqblk.dqb_ihardlimit == 0 &&
306: qup->dqblk.dqb_bsoftlimit == 0 &&
307: qup->dqblk.dqb_bhardlimit == 0)
308: continue;
309: msgi = (char *)0;
310: if (qup->dqblk.dqb_ihardlimit &&
311: qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit)
312: msgi = "File limit reached on";
313: else if (qup->dqblk.dqb_isoftlimit &&
314: qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit)
315: if (qup->dqblk.dqb_itime > now)
316: msgi = "In file grace period on";
317: else
318: msgi = "Over file quota on";
319: msgb = (char *)0;
320: if (qup->dqblk.dqb_bhardlimit &&
321: qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit)
322: msgb = "Block limit reached on";
323: else if (qup->dqblk.dqb_bsoftlimit &&
324: qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit)
325: if (qup->dqblk.dqb_btime > now)
326: msgb = "In block grace period on";
327: else
328: msgb = "Over block quota on";
329: if (qflag) {
330: if ((msgi != (char *)0 || msgb != (char *)0) &&
331: lines++ == 0)
332: heading(type, id, name, "");
333: if (msgi != (char *)0)
334: printf("\t%s %s\n", msgi, qup->fsname);
335: if (msgb != (char *)0)
336: printf("\t%s %s\n", msgb, qup->fsname);
337: continue;
338: }
339: if (vflag ||
340: qup->dqblk.dqb_curblocks ||
341: qup->dqblk.dqb_curinodes) {
342: if (lines++ == 0)
343: heading(type, id, name, "");
344: nam = qup->fsname;
345: if (strlen(qup->fsname) > 15) {
346: printf("%s\n", qup->fsname);
347: nam = "";
348: }
349: printf("%15s%8d%c%7d%8d%8s"
350: , nam
351: , dbtob(qup->dqblk.dqb_curblocks) / 1024
352: , (msgb == (char *)0) ? ' ' : '*'
353: , dbtob(qup->dqblk.dqb_bsoftlimit) / 1024
354: , dbtob(qup->dqblk.dqb_bhardlimit) / 1024
355: , (msgb == (char *)0) ? ""
356: : timeprt(qup->dqblk.dqb_btime));
357: printf("%8d%c%7d%8d%8s\n"
358: , qup->dqblk.dqb_curinodes
359: , (msgi == (char *)0) ? ' ' : '*'
360: , qup->dqblk.dqb_isoftlimit
361: , qup->dqblk.dqb_ihardlimit
362: , (msgi == (char *)0) ? ""
363: : timeprt(qup->dqblk.dqb_itime)
364: );
365: continue;
366: }
367: }
368: if (!qflag && lines == 0)
369: heading(type, id, name, "none");
370: }
371:
372: heading(type, id, name, tag)
373: int type;
374: u_long id;
375: char *name, *tag;
376: {
377:
378: printf("Disk quotas for %s %s (%cid %d): %s\n", qfextension[type],
379: name, *qfextension[type], id, tag);
380: if (!qflag && tag[0] == '\0') {
381: printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n"
382: , "Filesystem"
383: , "blocks"
384: , "quota"
385: , "limit"
386: , "grace"
387: , "files"
388: , "quota"
389: , "limit"
390: , "grace"
391: );
392: }
393: }
394:
395: /*
396: * Calculate the grace period and return a printable string for it.
397: */
398: char *
399: timeprt(seconds)
400: time_t seconds;
401: {
402: time_t hours, minutes;
403: static char buf[20];
404: static time_t now;
405:
406: if (now == 0)
407: time(&now);
408: if (now > seconds)
409: return ("none");
410: seconds -= now;
411: minutes = (seconds + 30) / 60;
412: hours = (minutes + 30) / 60;
413: if (hours >= 36) {
1.10 deraadt 414: snprintf(buf, sizeof buf, "%ddays", (hours + 12) / 24);
1.1 deraadt 415: return (buf);
416: }
417: if (minutes >= 60) {
1.10 deraadt 418: snprintf(buf, sizeof buf, "%2d:%d", minutes / 60, minutes % 60);
1.1 deraadt 419: return (buf);
420: }
1.10 deraadt 421: snprintf(buf, sizeof buf, "%2d", minutes);
1.1 deraadt 422: return (buf);
423: }
424:
425: /*
426: * Collect the requested quota information.
427: */
428: struct quotause *
429: getprivs(id, quotatype)
430: register long id;
431: int quotatype;
432: {
433: register struct quotause *qup, *quptail;
434: register struct fstab *fs;
435: struct quotause *quphead;
436: struct statfs *fst;
437: int nfst, i;
438:
439: qup = quphead = (struct quotause *)0;
440:
441: nfst = getmntinfo(&fst, MNT_WAIT);
442: if (nfst == 0) {
443: fprintf(stderr, "quota: no filesystems mounted!\n");
444: exit(2);
445: }
446: setfsent();
447: for (i=0; i<nfst; i++) {
448: if (qup == NULL) {
449: if ((qup = (struct quotause *)malloc(sizeof *qup)) == NULL) {
450: fprintf(stderr, "quota: out of memory\n");
451: exit(2);
452: }
453: }
454: if (strncmp(fst[i].f_fstypename, "nfs", MFSNAMELEN) == 0) {
455: if (getnfsquota(&fst[i], NULL, qup, id, quotatype) == 0)
456: continue;
1.4 deraadt 457: } else if (!strncmp(fst[i].f_fstypename, "ffs", MFSNAMELEN) ||
458: !strncmp(fst[i].f_fstypename, "ufs", MFSNAMELEN) ||
459: !strncmp(fst[i].f_fstypename, "mfs", MFSNAMELEN)) {
1.1 deraadt 460: /*
461: * XXX
462: * UFS filesystems must be in /etc/fstab, and must
463: * indicate that they have quotas on (?!) This is quite
464: * unlike SunOS where quotas can be enabled/disabled
465: * on a filesystem independent of /etc/fstab, and it
466: * will still print quotas for them.
467: */
468: if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
469: continue;
470: if (getufsquota(&fst[i], fs, qup, id, quotatype) == 0)
471: continue;
472: } else
473: continue;
1.10 deraadt 474: strncpy(qup->fsname, fst[i].f_mntonname, sizeof qup->fsname-1);
475: qup->fsname[sizeof qup->fsname-1] = '\0';
1.1 deraadt 476: if (quphead == NULL)
477: quphead = qup;
478: else
479: quptail->next = qup;
480: quptail = qup;
481: quptail->next = 0;
482: qup = NULL;
483: }
484: if (qup)
485: free(qup);
486: endfsent();
487: return (quphead);
488: }
489:
490: /*
491: * Check to see if a particular quota is to be enabled.
492: */
493: ufshasquota(fs, type, qfnamep)
494: register struct fstab *fs;
495: int type;
496: char **qfnamep;
497: {
498: static char initname, usrname[100], grpname[100];
499: static char buf[BUFSIZ];
500: char *opt, *cp;
501:
502: if (!initname) {
1.6 deraadt 503: snprintf(usrname, sizeof usrname, "%s%s",
504: qfextension[USRQUOTA], qfname);
505: snprintf(grpname, sizeof grpname, "%s%s",
506: qfextension[GRPQUOTA], qfname);
1.1 deraadt 507: initname = 1;
508: }
1.7 etheisen 509: strncpy(buf, fs->fs_mntops, sizeof buf);
1.1 deraadt 510: for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
1.9 millert 511: if (cp = strchr(opt, '='))
1.1 deraadt 512: *cp++ = '\0';
513: if (type == USRQUOTA && strcmp(opt, usrname) == 0)
514: break;
515: if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
516: break;
517: }
518: if (!opt)
519: return (0);
520: if (cp) {
521: *qfnamep = cp;
522: return (1);
523: }
1.6 deraadt 524: (void) snprintf(buf, sizeof buf, "%s/%s.%s",
525: fs->fs_file, qfname, qfextension[type]);
1.1 deraadt 526: *qfnamep = buf;
527: return (1);
528: }
529:
530: int
531: getufsquota(fst, fs, qup, id, quotatype)
532: struct statfs *fst;
533: struct fstab *fs;
534: struct quotause *qup;
535: long id;
536: int quotatype;
537: {
538: char *qfpathname;
539: int fd, qcmd;
540:
541: qcmd = QCMD(Q_GETQUOTA, quotatype);
542: if (!ufshasquota(fs, quotatype, &qfpathname))
543: return (0);
544:
545: if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
546: if ((fd = open(qfpathname, O_RDONLY)) < 0) {
547: perror(qfpathname);
548: return (0);
549: }
550: (void) lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET);
551: switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
552: case 0: /* EOF */
553: /*
554: * Convert implicit 0 quota (EOF)
555: * into an explicit one (zero'ed dqblk)
556: */
1.11 ! deraadt 557: memset((caddr_t)&qup->dqblk, 0, sizeof(struct dqblk));
1.1 deraadt 558: break;
559: case sizeof(struct dqblk): /* OK */
560: break;
561: default: /* ERROR */
562: fprintf(stderr, "quota: read error");
563: perror(qfpathname);
564: close(fd);
565: return (0);
566: }
567: close(fd);
568: }
569: return (1);
570: }
571:
572: int
573: getnfsquota(fst, fs, qup, id, quotatype)
574: struct statfs *fst;
575: struct fstab *fs;
576: struct quotause *qup;
577: long id;
578: int quotatype;
579: {
580: struct getquota_args gq_args;
581: struct getquota_rslt gq_rslt;
582: struct dqblk *dqp = &qup->dqblk;
583: struct timeval tv;
584: char *cp;
585:
586: if (fst->f_flags & MNT_LOCAL)
587: return (0);
588:
589: /*
590: * rpc.rquotad does not support group quotas
591: */
592: if (quotatype != USRQUOTA)
593: return (0);
594:
595: /*
596: * must be some form of "hostname:/path"
597: */
598: cp = strchr(fst->f_mntfromname, ':');
599: if (cp == NULL) {
600: fprintf(stderr, "cannot find hostname for %s\n",
601: fst->f_mntfromname);
602: return (0);
603: }
604:
605: *cp = '\0';
1.11 ! deraadt 606: if (cp[1] != '/') {
1.1 deraadt 607: *cp = ':';
608: return (0);
609: }
610:
1.11 ! deraadt 611: gq_args.gqa_pathp = &cp[1];
1.1 deraadt 612: gq_args.gqa_uid = id;
613: if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
614: RQUOTAPROC_GETQUOTA, xdr_getquota_args, &gq_args,
615: xdr_getquota_rslt, &gq_rslt) != 0) {
616: *cp = ':';
617: return (0);
618: }
619:
620: switch (gq_rslt.status) {
621: case Q_NOQUOTA:
622: break;
623: case Q_EPERM:
624: fprintf(stderr, "quota permission error, host: %s\n",
625: fst->f_mntfromname);
626: break;
627: case Q_OK:
628: gettimeofday(&tv, NULL);
629: /* blocks*/
630: dqp->dqb_bhardlimit =
631: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
632: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
633: dqp->dqb_bsoftlimit =
634: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
635: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
636: dqp->dqb_curblocks =
637: gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
638: gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE;
639: /* inodes */
640: dqp->dqb_ihardlimit =
641: gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
642: dqp->dqb_isoftlimit =
643: gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
644: dqp->dqb_curinodes =
645: gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
646: /* grace times */
647: dqp->dqb_btime =
648: tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
649: dqp->dqb_itime =
650: tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
651: *cp = ':';
652: return (1);
653: default:
654: fprintf(stderr, "bad rpc result, host: %s\n",
655: fst->f_mntfromname);
656: break;
657: }
658: *cp = ':';
659: return (0);
660: }
661:
662: int
663: callaurpc(host, prognum, versnum, procnum, inproc, in, outproc, out)
664: char *host;
665: xdrproc_t inproc, outproc;
666: char *in, *out;
667: {
668: struct sockaddr_in server_addr;
669: enum clnt_stat clnt_stat;
670: struct hostent *hp;
671: struct timeval timeout, tottimeout;
672:
673: CLIENT *client = NULL;
674: int socket = RPC_ANYSOCK;
675:
676: if ((hp = gethostbyname(host)) == NULL)
677: return ((int) RPC_UNKNOWNHOST);
678: timeout.tv_usec = 0;
679: timeout.tv_sec = 6;
1.11 ! deraadt 680:
! 681: memset(&server_addr, 0, sizeof server_addr);
! 682: memcpy(&server_addr.sin_addr, hp->h_addr, hp->h_length);
1.1 deraadt 683: server_addr.sin_family = AF_INET;
684: server_addr.sin_port = 0;
685:
686: if ((client = clntudp_create(&server_addr, prognum,
687: versnum, timeout, &socket)) == NULL)
688: return ((int) rpc_createerr.cf_stat);
689:
690: client->cl_auth = authunix_create_default();
691: tottimeout.tv_sec = 25;
692: tottimeout.tv_usec = 0;
693: clnt_stat = clnt_call(client, procnum, inproc, in,
694: outproc, out, tottimeout);
695:
696: return ((int) clnt_stat);
697: }
698:
699: alldigits(s)
700: register char *s;
701: {
702: register c;
703:
704: c = *s++;
705: do {
706: if (!isdigit(c))
707: return (0);
708: } while (c = *s++);
709: return (1);
710: }