xref: /freebsd/usr.sbin/edquota/edquota.c (revision e627b39baccd1ec9129690167cf5e6d860509655)
1 /*
2  * Copyright (c) 1980, 1990, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Robert Elz at The University of Melbourne.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. All advertising materials mentioning features or use of this software
17  *    must display the following acknowledgement:
18  *	This product includes software developed by the University of
19  *	California, Berkeley and its contributors.
20  * 4. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #ifndef lint
38 static char copyright[] =
39 "@(#) Copyright (c) 1980, 1990, 1993\n\
40 	The Regents of the University of California.  All rights reserved.\n";
41 #endif /* not lint */
42 
43 #ifndef lint
44 static char sccsid[] = "@(#)edquota.c	8.1 (Berkeley) 6/6/93";
45 #endif /* not lint */
46 
47 /*
48  * Disk quota editor.
49  */
50 #include <sys/param.h>
51 #include <sys/stat.h>
52 #include <sys/file.h>
53 #include <sys/wait.h>
54 #include <ufs/ufs/quota.h>
55 #include <errno.h>
56 #include <fstab.h>
57 #include <pwd.h>
58 #include <grp.h>
59 #include <ctype.h>
60 #include <stdio.h>
61 #include <string.h>
62 #include <unistd.h>
63 #include "pathnames.h"
64 
65 char *qfname = QUOTAFILENAME;
66 char *qfextension[] = INITQFNAMES;
67 char *quotagroup = QUOTAGROUP;
68 char tmpfil[] = _PATH_TMP;
69 
70 struct quotause {
71 	struct	quotause *next;
72 	long	flags;
73 	struct	dqblk dqblk;
74 	char	fsname[MAXPATHLEN + 1];
75 	char	qfname[1];	/* actually longer */
76 } *getprivs();
77 #define	FOUND	0x01
78 
79 main(argc, argv)
80 	register char **argv;
81 	int argc;
82 {
83 	register struct quotause *qup, *protoprivs, *curprivs;
84 	extern char *optarg;
85 	extern int optind;
86 	register long id, protoid;
87 	register int quotatype, tmpfd;
88 	register uid_t startuid, enduid;
89 	char *protoname, *cp, ch;
90 	int tflag = 0, pflag = 0;
91 	char buf[30];
92 
93 	if (argc < 2)
94 		usage();
95 	if (getuid()) {
96 		fprintf(stderr, "edquota: permission denied\n");
97 		exit(1);
98 	}
99 	quotatype = USRQUOTA;
100 	while ((ch = getopt(argc, argv, "ugtp:")) != EOF) {
101 		switch(ch) {
102 		case 'p':
103 			protoname = optarg;
104 			pflag++;
105 			break;
106 		case 'g':
107 			quotatype = GRPQUOTA;
108 			break;
109 		case 'u':
110 			quotatype = USRQUOTA;
111 			break;
112 		case 't':
113 			tflag++;
114 			break;
115 		default:
116 			usage();
117 		}
118 	}
119 	argc -= optind;
120 	argv += optind;
121 	if (pflag) {
122 		if ((protoid = getentry(protoname, quotatype)) == -1)
123 			exit(1);
124 		protoprivs = getprivs(protoid, quotatype);
125 		for (qup = protoprivs; qup; qup = qup->next) {
126 			qup->dqblk.dqb_btime = 0;
127 			qup->dqblk.dqb_itime = 0;
128 		}
129 		while (argc-- > 0) {
130 			if (isdigit(*argv[0]) &&
131 			    (cp = strchr(*argv, '-')) != NULL) {
132 				*cp++ = '\0';
133 				startuid = atoi(*argv);
134 				enduid = atoi(cp);
135 				if (enduid < startuid) {
136 					fprintf(stderr, "edquota: ending uid (%d) must be >= starting uid (%d) when using uid ranges\n",
137 						enduid, startuid);
138 					exit(1);
139 				}
140 				for ( ; startuid <= enduid; startuid++) {
141 					snprintf(buf, sizeof(buf), "%d",
142 					    startuid);
143 					if ((id = getentry(buf, quotatype)) < 0)
144 						continue;
145 					putprivs(id, quotatype, protoprivs);
146 				}
147 				continue;
148 			}
149 			if ((id = getentry(*argv++, quotatype)) < 0)
150 				continue;
151 			putprivs(id, quotatype, protoprivs);
152 		}
153 		exit(0);
154 	}
155 	tmpfd = mkstemp(tmpfil);
156 	fchown(tmpfd, getuid(), getgid());
157 	if (tflag) {
158 		protoprivs = getprivs(0, quotatype);
159 		if (writetimes(protoprivs, tmpfd, quotatype) == 0)
160 			exit(1);
161 		if (editit(tmpfil) && readtimes(protoprivs, tmpfil))
162 			putprivs(0, quotatype, protoprivs);
163 		freeprivs(protoprivs);
164 		exit(0);
165 	}
166 	for ( ; argc > 0; argc--, argv++) {
167 		if ((id = getentry(*argv, quotatype)) == -1)
168 			continue;
169 		curprivs = getprivs(id, quotatype);
170 		if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
171 			continue;
172 		if (editit(tmpfil) && readprivs(curprivs, tmpfil))
173 			putprivs(id, quotatype, curprivs);
174 		freeprivs(curprivs);
175 	}
176 	close(tmpfd);
177 	unlink(tmpfil);
178 	exit(0);
179 }
180 
181 usage()
182 {
183 	fprintf(stderr, "%s%s%s%s",
184 		"Usage: edquota [-u] [-p username] username ...\n",
185 		"\tedquota -g [-p groupname] groupname ...\n",
186 		"\tedquota [-u] -t\n", "\tedquota -g -t\n");
187 	exit(1);
188 }
189 
190 /*
191  * This routine converts a name for a particular quota type to
192  * an identifier. This routine must agree with the kernel routine
193  * getinoquota as to the interpretation of quota types.
194  */
195 getentry(name, quotatype)
196 	char *name;
197 	int quotatype;
198 {
199 	struct passwd *pw;
200 	struct group *gr;
201 
202 	if (alldigits(name))
203 		return (atoi(name));
204 	switch(quotatype) {
205 	case USRQUOTA:
206 		if (pw = getpwnam(name))
207 			return (pw->pw_uid);
208 		fprintf(stderr, "%s: no such user\n", name);
209 		break;
210 	case GRPQUOTA:
211 		if (gr = getgrnam(name))
212 			return (gr->gr_gid);
213 		fprintf(stderr, "%s: no such group\n", name);
214 		break;
215 	default:
216 		fprintf(stderr, "%d: unknown quota type\n", quotatype);
217 		break;
218 	}
219 	sleep(1);
220 	return (-1);
221 }
222 
223 /*
224  * Collect the requested quota information.
225  */
226 struct quotause *
227 getprivs(id, quotatype)
228 	register long id;
229 	int quotatype;
230 {
231 	register struct fstab *fs;
232 	register struct quotause *qup, *quptail;
233 	struct quotause *quphead;
234 	int qcmd, qupsize, fd;
235 	char *qfpathname;
236 	static int warned = 0;
237 	extern int errno;
238 
239 	setfsent();
240 	quphead = (struct quotause *)0;
241 	qcmd = QCMD(Q_GETQUOTA, quotatype);
242 	while (fs = getfsent()) {
243 		if (strcmp(fs->fs_vfstype, "ufs"))
244 			continue;
245 		if (!hasquota(fs, quotatype, &qfpathname))
246 			continue;
247 		qupsize = sizeof(*qup) + strlen(qfpathname);
248 		if ((qup = (struct quotause *)malloc(qupsize)) == NULL) {
249 			fprintf(stderr, "edquota: out of memory\n");
250 			exit(2);
251 		}
252 		if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
253 	    		if (errno == EOPNOTSUPP && !warned) {
254 				warned++;
255 				fprintf(stderr, "Warning: %s\n",
256 				    "Quotas are not compiled into this kernel");
257 				sleep(3);
258 			}
259 			if ((fd = open(qfpathname, O_RDONLY)) < 0) {
260 				fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
261 				if (fd < 0 && errno != ENOENT) {
262 					perror(qfpathname);
263 					free(qup);
264 					continue;
265 				}
266 				fprintf(stderr, "Creating quota file %s\n",
267 				    qfpathname);
268 				sleep(3);
269 				(void) fchown(fd, getuid(),
270 				    getentry(quotagroup, GRPQUOTA));
271 				(void) fchmod(fd, 0640);
272 			}
273 			lseek(fd, (long)(id * sizeof(struct dqblk)), L_SET);
274 			switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
275 			case 0:			/* EOF */
276 				/*
277 				 * Convert implicit 0 quota (EOF)
278 				 * into an explicit one (zero'ed dqblk)
279 				 */
280 				bzero((caddr_t)&qup->dqblk,
281 				    sizeof(struct dqblk));
282 				break;
283 
284 			case sizeof(struct dqblk):	/* OK */
285 				break;
286 
287 			default:		/* ERROR */
288 				fprintf(stderr, "edquota: read error in ");
289 				perror(qfpathname);
290 				close(fd);
291 				free(qup);
292 				continue;
293 			}
294 			close(fd);
295 		}
296 		strcpy(qup->qfname, qfpathname);
297 		strcpy(qup->fsname, fs->fs_file);
298 		if (quphead == NULL)
299 			quphead = qup;
300 		else
301 			quptail->next = qup;
302 		quptail = qup;
303 		qup->next = 0;
304 	}
305 	endfsent();
306 	return (quphead);
307 }
308 
309 /*
310  * Store the requested quota information.
311  */
312 putprivs(id, quotatype, quplist)
313 	long id;
314 	int quotatype;
315 	struct quotause *quplist;
316 {
317 	register struct quotause *qup;
318 	int qcmd, fd;
319 
320 	qcmd = QCMD(Q_SETQUOTA, quotatype);
321 	for (qup = quplist; qup; qup = qup->next) {
322 		if (quotactl(qup->fsname, qcmd, id, &qup->dqblk) == 0)
323 			continue;
324 		if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
325 			perror(qup->qfname);
326 		} else {
327 			lseek(fd, (long)id * (long)sizeof (struct dqblk), 0);
328 			if (write(fd, &qup->dqblk, sizeof (struct dqblk)) !=
329 			    sizeof (struct dqblk)) {
330 				fprintf(stderr, "edquota: ");
331 				perror(qup->qfname);
332 			}
333 			close(fd);
334 		}
335 	}
336 }
337 
338 /*
339  * Take a list of priviledges and get it edited.
340  */
341 editit(tmpfile)
342 	char *tmpfile;
343 {
344 	long omask;
345 	int pid, stat;
346 	extern char *getenv();
347 
348 	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
349  top:
350 	if ((pid = fork()) < 0) {
351 		extern errno;
352 
353 		if (errno == EPROCLIM) {
354 			fprintf(stderr, "You have too many processes\n");
355 			return(0);
356 		}
357 		if (errno == EAGAIN) {
358 			sleep(1);
359 			goto top;
360 		}
361 		perror("fork");
362 		return (0);
363 	}
364 	if (pid == 0) {
365 		register char *ed;
366 
367 		sigsetmask(omask);
368 		setgid(getgid());
369 		setuid(getuid());
370 		if ((ed = getenv("EDITOR")) == (char *)0)
371 			ed = _PATH_VI;
372 		execlp(ed, ed, tmpfile, 0);
373 		perror(ed);
374 		exit(1);
375 	}
376 	waitpid(pid, &stat, 0);
377 	sigsetmask(omask);
378 	if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0)
379 		return (0);
380 	return (1);
381 }
382 
383 /*
384  * Convert a quotause list to an ASCII file.
385  */
386 writeprivs(quplist, outfd, name, quotatype)
387 	struct quotause *quplist;
388 	int outfd;
389 	char *name;
390 	int quotatype;
391 {
392 	register struct quotause *qup;
393 	FILE *fd;
394 
395 	ftruncate(outfd, 0);
396 	lseek(outfd, 0, L_SET);
397 	if ((fd = fdopen(dup(outfd), "w")) == NULL) {
398 		fprintf(stderr, "edquota: ");
399 		perror(tmpfil);
400 		exit(1);
401 	}
402 	fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
403 	for (qup = quplist; qup; qup = qup->next) {
404 		fprintf(fd, "%s: %s %lu, limits (soft = %lu, hard = %lu)\n",
405 		    qup->fsname, "blocks in use:",
406 		    (unsigned long)(dbtob(qup->dqblk.dqb_curblocks) / 1024),
407 		    (unsigned long)(dbtob(qup->dqblk.dqb_bsoftlimit) / 1024),
408 		    (unsigned long)(dbtob(qup->dqblk.dqb_bhardlimit) / 1024));
409 		fprintf(fd, "%s %lu, limits (soft = %lu, hard = %lu)\n",
410 		    "\tinodes in use:", qup->dqblk.dqb_curinodes,
411 		    qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit);
412 	}
413 	fclose(fd);
414 	return (1);
415 }
416 
417 /*
418  * Merge changes to an ASCII file into a quotause list.
419  */
420 readprivs(quplist, inname)
421 	struct quotause *quplist;
422 	char *inname;
423 {
424 	register struct quotause *qup;
425 	FILE *fd;
426 	int cnt;
427 	register char *cp;
428 	struct dqblk dqblk;
429 	char *fsp, line1[BUFSIZ], line2[BUFSIZ];
430 
431 	fd = fopen(inname, "r");
432 	if (fd == NULL) {
433 		fprintf(stderr, "Can't re-read temp file!!\n");
434 		return (0);
435 	}
436 	/*
437 	 * Discard title line, then read pairs of lines to process.
438 	 */
439 	(void) fgets(line1, sizeof (line1), fd);
440 	while (fgets(line1, sizeof (line1), fd) != NULL &&
441 	       fgets(line2, sizeof (line2), fd) != NULL) {
442 		if ((fsp = strtok(line1, " \t:")) == NULL) {
443 			fprintf(stderr, "%s: bad format\n", line1);
444 			return (0);
445 		}
446 		if ((cp = strtok((char *)0, "\n")) == NULL) {
447 			fprintf(stderr, "%s: %s: bad format\n", fsp,
448 			    &fsp[strlen(fsp) + 1]);
449 			return (0);
450 		}
451 		cnt = sscanf(cp,
452 		    " blocks in use: %lu, limits (soft = %lu, hard = %lu)",
453 		    &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit,
454 		    &dqblk.dqb_bhardlimit);
455 		if (cnt != 3) {
456 			fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
457 			return (0);
458 		}
459 		dqblk.dqb_curblocks = btodb(dqblk.dqb_curblocks * 1024);
460 		dqblk.dqb_bsoftlimit = btodb(dqblk.dqb_bsoftlimit * 1024);
461 		dqblk.dqb_bhardlimit = btodb(dqblk.dqb_bhardlimit * 1024);
462 		if ((cp = strtok(line2, "\n")) == NULL) {
463 			fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
464 			return (0);
465 		}
466 		cnt = sscanf(cp,
467 		    "\tinodes in use: %lu, limits (soft = %lu, hard = %lu)",
468 		    &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit,
469 		    &dqblk.dqb_ihardlimit);
470 		if (cnt != 3) {
471 			fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
472 			return (0);
473 		}
474 		for (qup = quplist; qup; qup = qup->next) {
475 			if (strcmp(fsp, qup->fsname))
476 				continue;
477 			/*
478 			 * Cause time limit to be reset when the quota
479 			 * is next used if previously had no soft limit
480 			 * or were under it, but now have a soft limit
481 			 * and are over it.
482 			 */
483 			if (dqblk.dqb_bsoftlimit &&
484 			    qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
485 			    (qup->dqblk.dqb_bsoftlimit == 0 ||
486 			     qup->dqblk.dqb_curblocks <
487 			     qup->dqblk.dqb_bsoftlimit))
488 				qup->dqblk.dqb_btime = 0;
489 			if (dqblk.dqb_isoftlimit &&
490 			    qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
491 			    (qup->dqblk.dqb_isoftlimit == 0 ||
492 			     qup->dqblk.dqb_curinodes <
493 			     qup->dqblk.dqb_isoftlimit))
494 				qup->dqblk.dqb_itime = 0;
495 			qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
496 			qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
497 			qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
498 			qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
499 			qup->flags |= FOUND;
500 			if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
501 			    dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes)
502 				break;
503 			fprintf(stderr,
504 			    "%s: cannot change current allocation\n", fsp);
505 			break;
506 		}
507 	}
508 	fclose(fd);
509 	/*
510 	 * Disable quotas for any filesystems that have not been found.
511 	 */
512 	for (qup = quplist; qup; qup = qup->next) {
513 		if (qup->flags & FOUND) {
514 			qup->flags &= ~FOUND;
515 			continue;
516 		}
517 		qup->dqblk.dqb_bsoftlimit = 0;
518 		qup->dqblk.dqb_bhardlimit = 0;
519 		qup->dqblk.dqb_isoftlimit = 0;
520 		qup->dqblk.dqb_ihardlimit = 0;
521 	}
522 	return (1);
523 }
524 
525 /*
526  * Convert a quotause list to an ASCII file of grace times.
527  */
528 writetimes(quplist, outfd, quotatype)
529 	struct quotause *quplist;
530 	int outfd;
531 	int quotatype;
532 {
533 	register struct quotause *qup;
534 	char *cvtstoa();
535 	FILE *fd;
536 
537 	ftruncate(outfd, 0);
538 	lseek(outfd, 0, L_SET);
539 	if ((fd = fdopen(dup(outfd), "w")) == NULL) {
540 		fprintf(stderr, "edquota: ");
541 		perror(tmpfil);
542 		exit(1);
543 	}
544 	fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
545 	fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
546 	    qfextension[quotatype]);
547 	for (qup = quplist; qup; qup = qup->next) {
548 		fprintf(fd, "%s: block grace period: %s, ",
549 		    qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
550 		fprintf(fd, "file grace period: %s\n",
551 		    cvtstoa(qup->dqblk.dqb_itime));
552 	}
553 	fclose(fd);
554 	return (1);
555 }
556 
557 /*
558  * Merge changes of grace times in an ASCII file into a quotause list.
559  */
560 readtimes(quplist, inname)
561 	struct quotause *quplist;
562 	char *inname;
563 {
564 	register struct quotause *qup;
565 	FILE *fd;
566 	int cnt;
567 	register char *cp;
568 	time_t itime, btime, iseconds, bseconds;
569 	char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
570 
571 	fd = fopen(inname, "r");
572 	if (fd == NULL) {
573 		fprintf(stderr, "Can't re-read temp file!!\n");
574 		return (0);
575 	}
576 	/*
577 	 * Discard two title lines, then read lines to process.
578 	 */
579 	(void) fgets(line1, sizeof (line1), fd);
580 	(void) fgets(line1, sizeof (line1), fd);
581 	while (fgets(line1, sizeof (line1), fd) != NULL) {
582 		if ((fsp = strtok(line1, " \t:")) == NULL) {
583 			fprintf(stderr, "%s: bad format\n", line1);
584 			return (0);
585 		}
586 		if ((cp = strtok((char *)0, "\n")) == NULL) {
587 			fprintf(stderr, "%s: %s: bad format\n", fsp,
588 			    &fsp[strlen(fsp) + 1]);
589 			return (0);
590 		}
591 		cnt = sscanf(cp,
592 		    " block grace period: %ld %s file grace period: %ld %s",
593 		    &btime, bunits, &itime, iunits);
594 		if (cnt != 4) {
595 			fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
596 			return (0);
597 		}
598 		if (cvtatos(btime, bunits, &bseconds) == 0)
599 			return (0);
600 		if (cvtatos(itime, iunits, &iseconds) == 0)
601 			return (0);
602 		for (qup = quplist; qup; qup = qup->next) {
603 			if (strcmp(fsp, qup->fsname))
604 				continue;
605 			qup->dqblk.dqb_btime = bseconds;
606 			qup->dqblk.dqb_itime = iseconds;
607 			qup->flags |= FOUND;
608 			break;
609 		}
610 	}
611 	fclose(fd);
612 	/*
613 	 * reset default grace periods for any filesystems
614 	 * that have not been found.
615 	 */
616 	for (qup = quplist; qup; qup = qup->next) {
617 		if (qup->flags & FOUND) {
618 			qup->flags &= ~FOUND;
619 			continue;
620 		}
621 		qup->dqblk.dqb_btime = 0;
622 		qup->dqblk.dqb_itime = 0;
623 	}
624 	return (1);
625 }
626 
627 /*
628  * Convert seconds to ASCII times.
629  */
630 char *
631 cvtstoa(time)
632 	time_t time;
633 {
634 	static char buf[20];
635 
636 	if (time % (24 * 60 * 60) == 0) {
637 		time /= 24 * 60 * 60;
638 		sprintf(buf, "%ld day%s", time, time == 1 ? "" : "s");
639 	} else if (time % (60 * 60) == 0) {
640 		time /= 60 * 60;
641 		sprintf(buf, "%ld hour%s", time, time == 1 ? "" : "s");
642 	} else if (time % 60 == 0) {
643 		time /= 60;
644 		sprintf(buf, "%ld minute%s", time, time == 1 ? "" : "s");
645 	} else
646 		sprintf(buf, "%ld second%s", time, time == 1 ? "" : "s");
647 	return (buf);
648 }
649 
650 /*
651  * Convert ASCII input times to seconds.
652  */
653 cvtatos(time, units, seconds)
654 	time_t time;
655 	char *units;
656 	time_t *seconds;
657 {
658 
659 	if (bcmp(units, "second", 6) == 0)
660 		*seconds = time;
661 	else if (bcmp(units, "minute", 6) == 0)
662 		*seconds = time * 60;
663 	else if (bcmp(units, "hour", 4) == 0)
664 		*seconds = time * 60 * 60;
665 	else if (bcmp(units, "day", 3) == 0)
666 		*seconds = time * 24 * 60 * 60;
667 	else {
668 		printf("%s: bad units, specify %s\n", units,
669 		    "days, hours, minutes, or seconds");
670 		return (0);
671 	}
672 	return (1);
673 }
674 
675 /*
676  * Free a list of quotause structures.
677  */
678 freeprivs(quplist)
679 	struct quotause *quplist;
680 {
681 	register struct quotause *qup, *nextqup;
682 
683 	for (qup = quplist; qup; qup = nextqup) {
684 		nextqup = qup->next;
685 		free(qup);
686 	}
687 }
688 
689 /*
690  * Check whether a string is completely composed of digits.
691  */
692 alldigits(s)
693 	register char *s;
694 {
695 	register c;
696 
697 	c = *s++;
698 	do {
699 		if (!isdigit(c))
700 			return (0);
701 	} while (c = *s++);
702 	return (1);
703 }
704 
705 /*
706  * Check to see if a particular quota is to be enabled.
707  */
708 hasquota(fs, type, qfnamep)
709 	register struct fstab *fs;
710 	int type;
711 	char **qfnamep;
712 {
713 	register char *opt;
714 	char *cp, *index(), *strtok();
715 	static char initname, usrname[100], grpname[100];
716 	static char buf[BUFSIZ];
717 
718 	if (!initname) {
719 		sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
720 		sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
721 		initname = 1;
722 	}
723 	strcpy(buf, fs->fs_mntops);
724 	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
725 		if (cp = index(opt, '='))
726 			*cp++ = '\0';
727 		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
728 			break;
729 		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
730 			break;
731 	}
732 	if (!opt)
733 		return (0);
734 	if (cp) {
735 		*qfnamep = cp;
736 		return (1);
737 	}
738 	(void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
739 	*qfnamep = buf;
740 	return (1);
741 }
742