xref: /freebsd/usr.sbin/edquota/edquota.c (revision 4b9d6057)
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 editor.
37  */
38 
39 #include <sys/file.h>
40 #include <sys/mount.h>
41 #include <sys/wait.h>
42 #include <ufs/ufs/quota.h>
43 
44 #include <ctype.h>
45 #include <err.h>
46 #include <errno.h>
47 #include <fstab.h>
48 #include <grp.h>
49 #include <inttypes.h>
50 #include <libutil.h>
51 #include <pwd.h>
52 #include <signal.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 #include "pathnames.h"
59 
60 /* Let's be paranoid about block size */
61 #if 10 > DEV_BSHIFT
62 #define dbtokb(db) \
63 	((off_t)(db) >> (10-DEV_BSHIFT))
64 #elif 10 < DEV_BSHIFT
65 #define dbtokb(db) \
66 	((off_t)(db) << (DEV_BSHIFT-10))
67 #else
68 #define dbtokb(db)	(db)
69 #endif
70 
71 static const char *qfextension[] = INITQFNAMES;
72 static char tmpfil[] = _PATH_TMP;
73 static int hflag;
74 
75 struct quotause {
76 	struct	quotause *next;
77 	struct	quotafile *qf;
78 	struct	dqblk dqblk;
79 	int	flags;
80 	char	fsname[MAXPATHLEN + 1];
81 };
82 #define	FOUND	0x01
83 
84 int alldigits(const char *s);
85 int cvtatos(uint64_t, char *, uint64_t *);
86 char *cvtstoa(uint64_t);
87 uint64_t cvtblkval(uint64_t, char, const char *);
88 uint64_t cvtinoval(uint64_t, char, const char *);
89 int editit(char *);
90 char *fmthumanvalblks(int64_t);
91 char *fmthumanvalinos(int64_t);
92 void freeprivs(struct quotause *);
93 int getentry(const char *, int);
94 struct quotause *getprivs(long, int, char *);
95 void putprivs(long, struct quotause *);
96 int readprivs(struct quotause *, char *);
97 int readtimes(struct quotause *, char *);
98 static void usage(void) __dead2;
99 int writetimes(struct quotause *, int, int);
100 int writeprivs(struct quotause *, int, char *, int);
101 
102 int
103 main(int argc, char *argv[])
104 {
105 	struct quotause *qup, *protoprivs, *curprivs;
106 	long id, protoid;
107 	int i, quotatype, range, tmpfd;
108 	uid_t startuid, enduid;
109 	uint64_t lim;
110 	char *protoname, *cp, *endpt, *oldoptarg;
111 	int eflag = 0, tflag = 0, pflag = 0, ch;
112 	char *fspath = NULL;
113 	char buf[MAXLOGNAME];
114 
115 	if (argc < 2)
116 		usage();
117 	if (getuid())
118 		errx(1, "permission denied");
119 	quotatype = USRQUOTA;
120 	protoprivs = NULL;
121 	curprivs = NULL;
122 	protoname = NULL;
123 	while ((ch = getopt(argc, argv, "ughtf:p:e:")) != -1) {
124 		switch(ch) {
125 		case 'f':
126 			fspath = optarg;
127 			break;
128 		case 'p':
129 			if (eflag) {
130 				warnx("cannot specify both -e and -p");
131 				usage();
132 				/* not reached */
133 			}
134 			protoname = optarg;
135 			pflag++;
136 			break;
137 		case 'g':
138 			quotatype = GRPQUOTA;
139 			break;
140 		case 'h':
141 			hflag++;
142 			break;
143 		case 'u':
144 			quotatype = USRQUOTA;
145 			break;
146 		case 't':
147 			tflag++;
148 			break;
149 		case 'e':
150 			if (pflag) {
151 				warnx("cannot specify both -e and -p");
152 				usage();
153 				/* not reached */
154 			}
155 			if ((qup = calloc(1, sizeof(*qup))) == NULL)
156 				errx(2, "out of memory");
157 			oldoptarg = optarg;
158 			for (i = 0, cp = optarg;
159 			     (cp = strsep(&optarg, ":")) != NULL; i++) {
160 				if (cp != oldoptarg)
161 					*(cp - 1) = ':';
162 				if (i > 0 && !isdigit(*cp)) {
163 					warnx("incorrect quota specification: "
164 					    "%s", oldoptarg);
165 					usage();
166 					/* Not Reached */
167 				}
168 				switch (i) {
169 				case 0:
170 					strlcpy(qup->fsname, cp,
171 					    sizeof(qup->fsname));
172 					break;
173 				case 1:
174 					lim = strtoll(cp, &endpt, 10);
175 					qup->dqblk.dqb_bsoftlimit =
176 						cvtblkval(lim, *endpt,
177 						    "block soft limit");
178 					continue;
179 				case 2:
180 					lim = strtoll(cp, &endpt, 10);
181 					qup->dqblk.dqb_bhardlimit =
182 						cvtblkval(lim, *endpt,
183 						    "block hard limit");
184 					continue;
185 				case 3:
186 					lim = strtoll(cp, &endpt, 10);
187 					qup->dqblk.dqb_isoftlimit =
188 						cvtinoval(lim, *endpt,
189 						    "inode soft limit");
190 					continue;
191 				case 4:
192 					lim = strtoll(cp, &endpt, 10);
193 					qup->dqblk.dqb_ihardlimit =
194 						cvtinoval(lim, *endpt,
195 						    "inode hard limit");
196 					continue;
197 				default:
198 					warnx("incorrect quota specification: "
199 					    "%s", oldoptarg);
200 					usage();
201 					/* Not Reached */
202 				}
203 			}
204 			if (protoprivs == NULL) {
205 				protoprivs = curprivs = qup;
206 			} else {
207 				curprivs->next = qup;
208 				curprivs = qup;
209 			}
210 			eflag++;
211 			break;
212 		default:
213 			usage();
214 			/* Not Reached */
215 		}
216 	}
217 	argc -= optind;
218 	argv += optind;
219 	if (pflag || eflag) {
220 		if (pflag) {
221 			if ((protoid = getentry(protoname, quotatype)) == -1)
222 				exit(1);
223 			protoprivs = getprivs(protoid, quotatype, fspath);
224 			if (protoprivs == NULL)
225 				exit(0);
226 			for (qup = protoprivs; qup; qup = qup->next) {
227 				qup->dqblk.dqb_btime = 0;
228 				qup->dqblk.dqb_itime = 0;
229 			}
230 		}
231 		for (; argc-- > 0; argv++) {
232 			if (strspn(*argv, "0123456789-") == strlen(*argv) &&
233 			    (cp = strchr(*argv, '-')) != NULL) {
234 				*cp++ = '\0';
235 				startuid = atoi(*argv);
236 				enduid = atoi(cp);
237 				if (enduid < startuid)
238 					errx(1,
239 	"ending uid (%d) must be >= starting uid (%d) when using uid ranges",
240 						enduid, startuid);
241 				range = 1;
242 			} else {
243 				startuid = enduid = 0;
244 				range = 0;
245 			}
246 			for ( ; startuid <= enduid; startuid++) {
247 				if (range)
248 					snprintf(buf, sizeof(buf), "%d",
249 					    startuid);
250 				else
251 					snprintf(buf, sizeof(buf), "%s",
252 						*argv);
253 				if ((id = getentry(buf, quotatype)) < 0)
254 					continue;
255 				if (pflag) {
256 					putprivs(id, protoprivs);
257 					continue;
258 				}
259 				for (qup = protoprivs; qup; qup = qup->next) {
260 					curprivs = getprivs(id, quotatype,
261 					    qup->fsname);
262 					if (curprivs == NULL)
263 						continue;
264 					curprivs->dqblk = qup->dqblk;
265 					putprivs(id, curprivs);
266 					freeprivs(curprivs);
267 				}
268 			}
269 		}
270 		if (pflag)
271 			freeprivs(protoprivs);
272 		exit(0);
273 	}
274 	tmpfd = mkostemp(tmpfil, O_CLOEXEC);
275 	fchown(tmpfd, getuid(), getgid());
276 	if (tflag) {
277 		if ((protoprivs = getprivs(0, quotatype, fspath)) != NULL) {
278 			if (writetimes(protoprivs, tmpfd, quotatype) != 0 &&
279 			    editit(tmpfil) && readtimes(protoprivs, tmpfil))
280 				putprivs(0L, protoprivs);
281 			freeprivs(protoprivs);
282 		}
283 		close(tmpfd);
284 		unlink(tmpfil);
285 		exit(0);
286 	}
287 	for ( ; argc > 0; argc--, argv++) {
288 		if ((id = getentry(*argv, quotatype)) == -1)
289 			continue;
290 		if ((curprivs = getprivs(id, quotatype, fspath)) == NULL)
291 			exit(1);
292 		if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
293 			continue;
294 		if (editit(tmpfil) && readprivs(curprivs, tmpfil))
295 			putprivs(id, curprivs);
296 		freeprivs(curprivs);
297 	}
298 	close(tmpfd);
299 	unlink(tmpfil);
300 	exit(0);
301 }
302 
303 static void
304 usage(void)
305 {
306 	fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
307 		"usage: edquota [-uh] [-f fspath] [-p username] username ...",
308 		"       edquota [-u] -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
309 		"               username ...",
310 		"       edquota -g [-h] [-f fspath] [-p groupname] groupname ...",
311 		"       edquota -g -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
312 		"               groupname ...",
313 		"       edquota [-u] -t [-f fspath]",
314 		"       edquota -g -t [-f fspath]");
315 	exit(1);
316 }
317 
318 /*
319  * This routine converts a name for a particular quota type to
320  * an identifier. This routine must agree with the kernel routine
321  * getinoquota as to the interpretation of quota types.
322  */
323 int
324 getentry(const char *name, int quotatype)
325 {
326 	struct passwd *pw;
327 	struct group *gr;
328 
329 	if (alldigits(name))
330 		return (atoi(name));
331 	switch(quotatype) {
332 	case USRQUOTA:
333 		if ((pw = getpwnam(name)))
334 			return (pw->pw_uid);
335 		warnx("%s: no such user", name);
336 		sleep(3);
337 		break;
338 	case GRPQUOTA:
339 		if ((gr = getgrnam(name)))
340 			return (gr->gr_gid);
341 		warnx("%s: no such group", name);
342 		sleep(3);
343 		break;
344 	default:
345 		warnx("%d: unknown quota type", quotatype);
346 		sleep(3);
347 		break;
348 	}
349 	sleep(1);
350 	return (-1);
351 }
352 
353 /*
354  * Collect the requested quota information.
355  */
356 struct quotause *
357 getprivs(long id, int quotatype, char *fspath)
358 {
359 	struct quotafile *qf;
360 	struct fstab *fs;
361 	struct quotause *qup, *quptail;
362 	struct quotause *quphead;
363 
364 	setfsent();
365 	quphead = quptail = NULL;
366 	while ((fs = getfsent())) {
367 		if (fspath && *fspath && strcmp(fspath, fs->fs_spec) &&
368 		    strcmp(fspath, fs->fs_file))
369 			continue;
370 		if (strcmp(fs->fs_vfstype, "ufs"))
371 			continue;
372 		if ((qf = quota_open(fs, quotatype, O_CREAT|O_RDWR)) == NULL) {
373 			if (errno != EOPNOTSUPP)
374 				warn("cannot open quotas on %s", fs->fs_file);
375 			continue;
376 		}
377 		if ((qup = (struct quotause *)calloc(1, sizeof(*qup))) == NULL)
378 			errx(2, "out of memory");
379 		qup->qf = qf;
380 		strlcpy(qup->fsname, fs->fs_file, sizeof(qup->fsname));
381 		if (quota_read(qf, &qup->dqblk, id) == -1) {
382 			warn("cannot read quotas on %s", fs->fs_file);
383 			freeprivs(qup);
384 			continue;
385 		}
386 		if (quphead == NULL)
387 			quphead = qup;
388 		else
389 			quptail->next = qup;
390 		quptail = qup;
391 		qup->next = 0;
392 	}
393 	if (quphead == NULL) {
394 		warnx("No quotas on %s", fspath ? fspath : "any filesystems");
395 	}
396 	endfsent();
397 	return (quphead);
398 }
399 
400 /*
401  * Store the requested quota information.
402  */
403 void
404 putprivs(long id, struct quotause *quplist)
405 {
406 	struct quotause *qup;
407 
408 	for (qup = quplist; qup; qup = qup->next)
409 		if (quota_write_limits(qup->qf, &qup->dqblk, id) == -1)
410 			warn("%s", qup->fsname);
411 }
412 
413 /*
414  * Take a list of privileges and get it edited.
415  */
416 int
417 editit(char *tmpf)
418 {
419 	long omask;
420 	int pid, status;
421 
422 	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
423  top:
424 	if ((pid = fork()) < 0) {
425 
426 		if (errno == EPROCLIM) {
427 			warnx("you have too many processes");
428 			return(0);
429 		}
430 		if (errno == EAGAIN) {
431 			sleep(1);
432 			goto top;
433 		}
434 		warn("fork");
435 		return (0);
436 	}
437 	if (pid == 0) {
438 		const char *ed;
439 
440 		sigsetmask(omask);
441 		if (setgid(getgid()) != 0)
442 			err(1, "setgid failed");
443 		if (setuid(getuid()) != 0)
444 			err(1, "setuid failed");
445 		if ((ed = getenv("EDITOR")) == (char *)0)
446 			ed = _PATH_VI;
447 		execlp(ed, ed, tmpf, (char *)0);
448 		err(1, "%s", ed);
449 	}
450 	waitpid(pid, &status, 0);
451 	sigsetmask(omask);
452 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
453 		return (0);
454 	return (1);
455 }
456 
457 /*
458  * Convert a quotause list to an ASCII file.
459  */
460 int
461 writeprivs(struct quotause *quplist, int outfd, char *name, int quotatype)
462 {
463 	struct quotause *qup;
464 	FILE *fd;
465 
466 	ftruncate(outfd, 0);
467 	lseek(outfd, 0, L_SET);
468 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
469 		err(1, "%s", tmpfil);
470 	fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
471 	for (qup = quplist; qup; qup = qup->next) {
472 		fprintf(fd, "%s: in use: %s, ", qup->fsname,
473 		    fmthumanvalblks(qup->dqblk.dqb_curblocks));
474 		fprintf(fd, "limits (soft = %s, ",
475 		    fmthumanvalblks(qup->dqblk.dqb_bsoftlimit));
476 		fprintf(fd, "hard = %s)\n",
477 		    fmthumanvalblks(qup->dqblk.dqb_bhardlimit));
478 		fprintf(fd, "\tinodes in use: %s, ",
479 		    fmthumanvalinos(qup->dqblk.dqb_curinodes));
480 		fprintf(fd, "limits (soft = %s, ",
481 		    fmthumanvalinos(qup->dqblk.dqb_isoftlimit));
482 		fprintf(fd, "hard = %s)\n",
483 		    fmthumanvalinos(qup->dqblk.dqb_ihardlimit));
484 	}
485 	fclose(fd);
486 	return (1);
487 }
488 
489 char *
490 fmthumanvalblks(int64_t blocks)
491 {
492 	static char numbuf[20];
493 
494 	if (hflag) {
495 		humanize_number(numbuf, blocks < 0 ? 7 : 6,
496 		    dbtob(blocks), "", HN_AUTOSCALE, HN_NOSPACE);
497 		return (numbuf);
498 	}
499 	snprintf(numbuf, sizeof(numbuf), "%juk", (uintmax_t)dbtokb(blocks));
500 	return(numbuf);
501 }
502 
503 char *
504 fmthumanvalinos(int64_t inos)
505 {
506 	static char numbuf[20];
507 
508 	if (hflag) {
509 		humanize_number(numbuf, inos < 0 ? 7 : 6,
510 		    inos, "", HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
511 		return (numbuf);
512 	}
513 	snprintf(numbuf, sizeof(numbuf), "%ju", (uintmax_t)inos);
514 	return(numbuf);
515 }
516 
517 /*
518  * Merge changes to an ASCII file into a quotause list.
519  */
520 int
521 readprivs(struct quotause *quplist, char *inname)
522 {
523 	struct quotause *qup;
524 	FILE *fd;
525 	uintmax_t hardlimit, softlimit, curitems;
526 	char hardunits, softunits, curitemunits;
527 	int cnt;
528 	char *cp;
529 	struct dqblk dqblk;
530 	char *fsp, line1[BUFSIZ], line2[BUFSIZ];
531 
532 	fd = fopen(inname, "r");
533 	if (fd == NULL) {
534 		warnx("can't re-read temp file!!");
535 		return (0);
536 	}
537 	/*
538 	 * Discard title line, then read pairs of lines to process.
539 	 */
540 	(void) fgets(line1, sizeof (line1), fd);
541 	while (fgets(line1, sizeof (line1), fd) != NULL &&
542 	       fgets(line2, sizeof (line2), fd) != NULL) {
543 		if ((fsp = strtok(line1, " \t:")) == NULL) {
544 			warnx("%s: bad format", line1);
545 			return (0);
546 		}
547 		if ((cp = strtok((char *)0, "\n")) == NULL) {
548 			warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
549 			return (0);
550 		}
551 		cnt = sscanf(cp,
552 		    " in use: %ju%c, limits (soft = %ju%c, hard = %ju%c)",
553 		    &curitems, &curitemunits, &softlimit, &softunits,
554 		    &hardlimit, &hardunits);
555 		/*
556 		 * The next three check for old-style input formats.
557 		 */
558 		if (cnt != 6)
559 			cnt = sscanf(cp,
560 			 " in use: %ju%c, limits (soft = %ju%c hard = %ju%c",
561 			    &curitems, &curitemunits, &softlimit,
562 			    &softunits, &hardlimit, &hardunits);
563 		if (cnt != 6)
564 			cnt = sscanf(cp,
565 			" in use: %ju%c, limits (soft = %ju%c hard = %ju%c)",
566 			    &curitems, &curitemunits, &softlimit,
567 			    &softunits, &hardlimit, &hardunits);
568 		if (cnt != 6)
569 			cnt = sscanf(cp,
570 			" in use: %ju%c, limits (soft = %ju%c, hard = %ju%c",
571 			    &curitems, &curitemunits, &softlimit,
572 			    &softunits, &hardlimit, &hardunits);
573 		if (cnt != 6) {
574 			warnx("%s:%s: bad format", fsp, cp);
575 			return (0);
576 		}
577 		dqblk.dqb_curblocks = cvtblkval(curitems, curitemunits,
578 		    "current block count");
579 		dqblk.dqb_bsoftlimit = cvtblkval(softlimit, softunits,
580 		    "block soft limit");
581 		dqblk.dqb_bhardlimit = cvtblkval(hardlimit, hardunits,
582 		    "block hard limit");
583 		if ((cp = strtok(line2, "\n")) == NULL) {
584 			warnx("%s: %s: bad format", fsp, line2);
585 			return (0);
586 		}
587 		cnt = sscanf(&cp[7],
588 		    " in use: %ju%c limits (soft = %ju%c, hard = %ju%c)",
589 		    &curitems, &curitemunits, &softlimit,
590 		    &softunits, &hardlimit, &hardunits);
591 		/*
592 		 * The next three check for old-style input formats.
593 		 */
594 		if (cnt != 6)
595 			cnt = sscanf(&cp[7],
596 			 " in use: %ju%c limits (soft = %ju%c hard = %ju%c",
597 			    &curitems, &curitemunits, &softlimit,
598 			    &softunits, &hardlimit, &hardunits);
599 		if (cnt != 6)
600 			cnt = sscanf(&cp[7],
601 			" in use: %ju%c limits (soft = %ju%c hard = %ju%c)",
602 			    &curitems, &curitemunits, &softlimit,
603 			    &softunits, &hardlimit, &hardunits);
604 		if (cnt != 6)
605 			cnt = sscanf(&cp[7],
606 			" in use: %ju%c limits (soft = %ju%c, hard = %ju%c",
607 			    &curitems, &curitemunits, &softlimit,
608 			    &softunits, &hardlimit, &hardunits);
609 		if (cnt != 6) {
610 			warnx("%s: %s: bad format cnt %d", fsp, &cp[7], cnt);
611 			return (0);
612 		}
613 		dqblk.dqb_curinodes = cvtinoval(curitems, curitemunits,
614 		    "current inode count");
615 		dqblk.dqb_isoftlimit = cvtinoval(softlimit, softunits,
616 		    "inode soft limit");
617 		dqblk.dqb_ihardlimit = cvtinoval(hardlimit, hardunits,
618 		    "inode hard limit");
619 		for (qup = quplist; qup; qup = qup->next) {
620 			if (strcmp(fsp, qup->fsname))
621 				continue;
622 			/*
623 			 * Cause time limit to be reset when the quota
624 			 * is next used if previously had no soft limit
625 			 * or were under it, but now have a soft limit
626 			 * and are over it.
627 			 */
628 			if (dqblk.dqb_bsoftlimit &&
629 			    qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
630 			    (qup->dqblk.dqb_bsoftlimit == 0 ||
631 			     qup->dqblk.dqb_curblocks <
632 			     qup->dqblk.dqb_bsoftlimit))
633 				qup->dqblk.dqb_btime = 0;
634 			if (dqblk.dqb_isoftlimit &&
635 			    qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
636 			    (qup->dqblk.dqb_isoftlimit == 0 ||
637 			     qup->dqblk.dqb_curinodes <
638 			     qup->dqblk.dqb_isoftlimit))
639 				qup->dqblk.dqb_itime = 0;
640 			qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
641 			qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
642 			qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
643 			qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
644 			qup->flags |= FOUND;
645 			/* Humanized input returns only approximate counts */
646 			if (hflag ||
647 			    (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
648 			     dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes))
649 				break;
650 			warnx("%s: cannot change current allocation", fsp);
651 			break;
652 		}
653 	}
654 	fclose(fd);
655 	/*
656 	 * Disable quotas for any filesystems that have not been found.
657 	 */
658 	for (qup = quplist; qup; qup = qup->next) {
659 		if (qup->flags & FOUND) {
660 			qup->flags &= ~FOUND;
661 			continue;
662 		}
663 		qup->dqblk.dqb_bsoftlimit = 0;
664 		qup->dqblk.dqb_bhardlimit = 0;
665 		qup->dqblk.dqb_isoftlimit = 0;
666 		qup->dqblk.dqb_ihardlimit = 0;
667 	}
668 	return (1);
669 }
670 
671 /*
672  * Convert a quotause list to an ASCII file of grace times.
673  */
674 int
675 writetimes(struct quotause *quplist, int outfd, int quotatype)
676 {
677 	struct quotause *qup;
678 	FILE *fd;
679 
680 	ftruncate(outfd, 0);
681 	lseek(outfd, 0, L_SET);
682 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
683 		err(1, "%s", tmpfil);
684 	fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
685 	fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
686 	    qfextension[quotatype]);
687 	for (qup = quplist; qup; qup = qup->next) {
688 		fprintf(fd, "%s: block grace period: %s, ",
689 		    qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
690 		fprintf(fd, "file grace period: %s\n",
691 		    cvtstoa(qup->dqblk.dqb_itime));
692 	}
693 	fclose(fd);
694 	return (1);
695 }
696 
697 /*
698  * Merge changes of grace times in an ASCII file into a quotause list.
699  */
700 int
701 readtimes(struct quotause *quplist, char *inname)
702 {
703 	struct quotause *qup;
704 	FILE *fd;
705 	int cnt;
706 	char *cp;
707 	uintmax_t itime, btime, iseconds, bseconds;
708 	char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
709 
710 	fd = fopen(inname, "r");
711 	if (fd == NULL) {
712 		warnx("can't re-read temp file!!");
713 		return (0);
714 	}
715 	/*
716 	 * Discard two title lines, then read lines to process.
717 	 */
718 	(void) fgets(line1, sizeof (line1), fd);
719 	(void) fgets(line1, sizeof (line1), fd);
720 	while (fgets(line1, sizeof (line1), fd) != NULL) {
721 		if ((fsp = strtok(line1, " \t:")) == NULL) {
722 			warnx("%s: bad format", line1);
723 			return (0);
724 		}
725 		if ((cp = strtok((char *)0, "\n")) == NULL) {
726 			warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
727 			return (0);
728 		}
729 		cnt = sscanf(cp,
730 		    " block grace period: %ju %s file grace period: %ju %s",
731 		    &btime, bunits, &itime, iunits);
732 		if (cnt != 4) {
733 			warnx("%s:%s: bad format", fsp, cp);
734 			return (0);
735 		}
736 		if (cvtatos(btime, bunits, &bseconds) == 0)
737 			return (0);
738 		if (cvtatos(itime, iunits, &iseconds) == 0)
739 			return (0);
740 		for (qup = quplist; qup; qup = qup->next) {
741 			if (strcmp(fsp, qup->fsname))
742 				continue;
743 			qup->dqblk.dqb_btime = bseconds;
744 			qup->dqblk.dqb_itime = iseconds;
745 			qup->flags |= FOUND;
746 			break;
747 		}
748 	}
749 	fclose(fd);
750 	/*
751 	 * reset default grace periods for any filesystems
752 	 * that have not been found.
753 	 */
754 	for (qup = quplist; qup; qup = qup->next) {
755 		if (qup->flags & FOUND) {
756 			qup->flags &= ~FOUND;
757 			continue;
758 		}
759 		qup->dqblk.dqb_btime = 0;
760 		qup->dqblk.dqb_itime = 0;
761 	}
762 	return (1);
763 }
764 
765 /*
766  * Convert seconds to ASCII times.
767  */
768 char *
769 cvtstoa(uint64_t secs)
770 {
771 	static char buf[20];
772 
773 	if (secs % (24 * 60 * 60) == 0) {
774 		secs /= 24 * 60 * 60;
775 		sprintf(buf, "%ju day%s", (uintmax_t)secs,
776 		    secs == 1 ? "" : "s");
777 	} else if (secs % (60 * 60) == 0) {
778 		secs /= 60 * 60;
779 		sprintf(buf, "%ju hour%s", (uintmax_t)secs,
780 		    secs == 1 ? "" : "s");
781 	} else if (secs % 60 == 0) {
782 		secs /= 60;
783 		sprintf(buf, "%ju minute%s", (uintmax_t)secs,
784 		    secs == 1 ? "" : "s");
785 	} else
786 		sprintf(buf, "%ju second%s", (uintmax_t)secs,
787 		    secs == 1 ? "" : "s");
788 	return (buf);
789 }
790 
791 /*
792  * Convert ASCII input times to seconds.
793  */
794 int
795 cvtatos(uint64_t period, char *units, uint64_t *seconds)
796 {
797 
798 	if (bcmp(units, "second", 6) == 0)
799 		*seconds = period;
800 	else if (bcmp(units, "minute", 6) == 0)
801 		*seconds = period * 60;
802 	else if (bcmp(units, "hour", 4) == 0)
803 		*seconds = period * 60 * 60;
804 	else if (bcmp(units, "day", 3) == 0)
805 		*seconds = period * 24 * 60 * 60;
806 	else {
807 		warnx("%s: bad units, specify %s\n", units,
808 		    "days, hours, minutes, or seconds");
809 		return (0);
810 	}
811 	return (1);
812 }
813 
814 /*
815  * Convert a limit to number of disk blocks.
816  */
817 uint64_t
818 cvtblkval(uint64_t limit, char units, const char *itemname)
819 {
820 
821 	switch(units) {
822 	case 'B':
823 	case 'b':
824 		limit = btodb(limit);
825 		break;
826 	case '\0':	/* historic behavior */
827 	case ',':	/* historic behavior */
828 	case ')':	/* historic behavior */
829 	case 'K':
830 	case 'k':
831 		limit *= btodb(1024);
832 		break;
833 	case 'M':
834 	case 'm':
835 		limit *= btodb(1048576);
836 		break;
837 	case 'G':
838 	case 'g':
839 		limit *= btodb(1073741824);
840 		break;
841 	case 'T':
842 	case 't':
843 		limit *= btodb(1099511627776);
844 		break;
845 	case 'P':
846 	case 'p':
847 		limit *= btodb(1125899906842624);
848 		break;
849 	case 'E':
850 	case 'e':
851 		limit *= btodb(1152921504606846976);
852 		break;
853 	case ' ':
854 		errx(2, "No space permitted between value and units for %s\n",
855 		    itemname);
856 		break;
857 	default:
858 		errx(2, "%ju%c: unknown units for %s, specify "
859 		    "none, K, M, G, T, P, or E\n",
860 		    (uintmax_t)limit, units, itemname);
861 		break;
862 	}
863 	return (limit);
864 }
865 
866 /*
867  * Convert a limit to number of inodes.
868  */
869 uint64_t
870 cvtinoval(uint64_t limit, char units, const char *itemname)
871 {
872 
873 	switch(units) {
874 	case 'B':
875 	case 'b':
876 	case '\0':	/* historic behavior */
877 	case ',':	/* historic behavior */
878 	case ')':	/* historic behavior */
879 		break;
880 	case 'K':
881 	case 'k':
882 		limit *= 1000;
883 		break;
884 	case 'M':
885 	case 'm':
886 		limit *= 1000000;
887 		break;
888 	case 'G':
889 	case 'g':
890 		limit *= 1000000000;
891 		break;
892 	case 'T':
893 	case 't':
894 		limit *= 1000000000000;
895 		break;
896 	case 'P':
897 	case 'p':
898 		limit *= 1000000000000000;
899 		break;
900 	case 'E':
901 	case 'e':
902 		limit *= 1000000000000000000;
903 		break;
904 	case ' ':
905 		errx(2, "No space permitted between value and units for %s\n",
906 		    itemname);
907 		break;
908 	default:
909 		errx(2, "%ju%c: unknown units for %s, specify "
910 		    "none, K, M, G, T, P, or E\n",
911 		    (uintmax_t)limit, units, itemname);
912 		break;
913 	}
914 	return (limit);
915 }
916 
917 /*
918  * Free a list of quotause structures.
919  */
920 void
921 freeprivs(struct quotause *quplist)
922 {
923 	struct quotause *qup, *nextqup;
924 
925 	for (qup = quplist; qup; qup = nextqup) {
926 		quota_close(qup->qf);
927 		nextqup = qup->next;
928 		free(qup);
929 	}
930 }
931 
932 /*
933  * Check whether a string is completely composed of digits.
934  */
935 int
936 alldigits(const char *s)
937 {
938 	int c;
939 
940 	c = *s++;
941 	do {
942 		if (!isdigit(c))
943 			return (0);
944 	} while ((c = *s++));
945 	return (1);
946 }
947