xref: /netbsd-src/usr.sbin/edquota/edquota.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*      $NetBSD: edquota.c,v 1.52 2012/08/14 04:53:43 dholland Exp $ */
2 /*
3  * Copyright (c) 1980, 1990, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Robert Elz at The University of Melbourne.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/cdefs.h>
35 #ifndef lint
36 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\
37  The Regents of the University of California.  All rights reserved.");
38 #endif /* not lint */
39 
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "from: @(#)edquota.c	8.3 (Berkeley) 4/27/95";
43 #else
44 __RCSID("$NetBSD: edquota.c,v 1.52 2012/08/14 04:53:43 dholland Exp $");
45 #endif
46 #endif /* not lint */
47 
48 /*
49  * Disk quota editor.
50  */
51 #include <sys/param.h>
52 #include <sys/stat.h>
53 #include <sys/file.h>
54 #include <sys/wait.h>
55 #include <sys/queue.h>
56 #include <sys/types.h>
57 #include <sys/statvfs.h>
58 
59 #include <quota.h>
60 
61 #include <assert.h>
62 #include <err.h>
63 #include <errno.h>
64 #include <fstab.h>
65 #include <pwd.h>
66 #include <grp.h>
67 #include <ctype.h>
68 #include <signal.h>
69 #include <stdbool.h>
70 #include <stdio.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74 #include <util.h>
75 
76 #include "printquota.h"
77 
78 #include "pathnames.h"
79 
80 /*
81  * XXX. Ideally we shouldn't compile this in, but it'll take some
82  * reworking to avoid it and it'll be ok for now.
83  */
84 #define EDQUOTA_NUMOBJTYPES	2
85 
86 #if 0
87 static const char *quotagroup = QUOTAGROUP;
88 #endif
89 
90 #define MAX_TMPSTR	(100+MAXPATHLEN)
91 
92 enum sources {
93 	SRC_EDITED,	/* values came from user */
94 	SRC_QUOTA,	/* values came from a specific quota entry */
95 	SRC_DEFAULT,	/* values were copied from the default quota entry */
96 	SRC_CLEAR,	/* values arose by zeroing out a quota entry */
97 };
98 
99 struct quotause {
100 	struct	quotause *next;
101 	unsigned found:1,	/* found after running editor */
102 		xgrace:1,	/* grace periods are per-id */
103 		isdefault:1;
104 
105 	struct	quotaval qv[EDQUOTA_NUMOBJTYPES];
106 	enum sources source[EDQUOTA_NUMOBJTYPES];
107 	char	fsname[MAXPATHLEN + 1];
108 	char	implementation[32];
109 };
110 
111 struct quotalist {
112 	struct quotause *head;
113 	struct quotause *tail;
114 	char *idtypename;
115 };
116 
117 static void	usage(void) __dead;
118 
119 static int Hflag = 0;
120 
121 /* more compact form of constants */
122 #define QO_BLK QUOTA_OBJTYPE_BLOCKS
123 #define QO_FL  QUOTA_OBJTYPE_FILES
124 
125 ////////////////////////////////////////////////////////////
126 // support code
127 
128 /*
129  * This routine converts a name for a particular quota class to
130  * an identifier. This routine must agree with the kernel routine
131  * getinoquota as to the interpretation of quota classes.
132  */
133 static int
134 getidbyname(const char *name, int idtype)
135 {
136 	struct passwd *pw;
137 	struct group *gr;
138 
139 	if (alldigits(name))
140 		return atoi(name);
141 	switch (idtype) {
142 	case QUOTA_IDTYPE_USER:
143 		if ((pw = getpwnam(name)) != NULL)
144 			return pw->pw_uid;
145 		warnx("%s: no such user", name);
146 		break;
147 	case QUOTA_IDTYPE_GROUP:
148 		if ((gr = getgrnam(name)) != NULL)
149 			return gr->gr_gid;
150 		warnx("%s: no such group", name);
151 		break;
152 	default:
153 		warnx("%d: unknown quota type", idtype);
154 		break;
155 	}
156 	sleep(1);
157 	return -1;
158 }
159 
160 /*
161  * check if a source is "real" (reflects actual data) or not
162  */
163 static bool
164 source_is_real(enum sources source)
165 {
166 	switch (source) {
167 	    case SRC_EDITED:
168 	    case SRC_QUOTA:
169 		return true;
170 	    case SRC_DEFAULT:
171 	    case SRC_CLEAR:
172 		return false;
173 	}
174 	assert(!"encountered invalid source");
175 	return false;
176 }
177 
178 /*
179  * some simple string tools
180  */
181 
182 static /*const*/ char *
183 skipws(/*const*/ char *s)
184 {
185 	while (*s == ' ' || *s == '\t') {
186 		s++;
187 	}
188 	return s;
189 }
190 
191 static /*const*/ char *
192 skipword(/*const*/ char *s)
193 {
194 	while (*s != '\0' && *s != '\n' && *s != ' ' && *s != '\t') {
195 		s++;
196 	}
197 	return s;
198 }
199 
200 ////////////////////////////////////////////////////////////
201 // quotause operations
202 
203 /*
204  * Create an empty quotause structure.
205  */
206 static struct quotause *
207 quotause_create(void)
208 {
209 	struct quotause *qup;
210 	unsigned i;
211 
212 	qup = malloc(sizeof(*qup));
213 	if (qup == NULL) {
214 		err(1, "malloc");
215 	}
216 
217 	qup->next = NULL;
218 	qup->found = 0;
219 	qup->xgrace = 0;
220 	qup->isdefault = 0;
221 	for (i=0; i<EDQUOTA_NUMOBJTYPES; i++) {
222 		quotaval_clear(&qup->qv[i]);
223 		qup->source[i] = SRC_CLEAR;
224 	}
225 	qup->fsname[0] = '\0';
226 
227 	return qup;
228 }
229 
230 /*
231  * Free a quotause structure.
232  */
233 static void
234 quotause_destroy(struct quotause *qup)
235 {
236 	free(qup);
237 }
238 
239 ////////////////////////////////////////////////////////////
240 // quotalist operations
241 
242 /*
243  * Create a quotause list.
244  */
245 static struct quotalist *
246 quotalist_create(void)
247 {
248 	struct quotalist *qlist;
249 
250 	qlist = malloc(sizeof(*qlist));
251 	if (qlist == NULL) {
252 		err(1, "malloc");
253 	}
254 
255 	qlist->head = NULL;
256 	qlist->tail = NULL;
257 	qlist->idtypename = NULL;
258 
259 	return qlist;
260 }
261 
262 /*
263  * Free a list of quotause structures.
264  */
265 static void
266 quotalist_destroy(struct quotalist *qlist)
267 {
268 	struct quotause *qup, *nextqup;
269 
270 	for (qup = qlist->head; qup; qup = nextqup) {
271 		nextqup = qup->next;
272 		quotause_destroy(qup);
273 	}
274 	free(qlist->idtypename);
275 	free(qlist);
276 }
277 
278 #if 0
279 static bool
280 quotalist_empty(struct quotalist *qlist)
281 {
282 	return qlist->head == NULL;
283 }
284 #endif
285 
286 static void
287 quotalist_append(struct quotalist *qlist, struct quotause *qup)
288 {
289 	/* should not already be on a list */
290 	assert(qup->next == NULL);
291 
292 	if (qlist->head == NULL) {
293 		qlist->head = qup;
294 	} else {
295 		qlist->tail->next = qup;
296 	}
297 	qlist->tail = qup;
298 }
299 
300 ////////////////////////////////////////////////////////////
301 // ffs quota v1
302 
303 #if 0
304 static void
305 putprivs1(uint32_t id, int idtype, struct quotause *qup)
306 {
307 	struct dqblk dqblk;
308 	int fd;
309 
310 	quotavals_to_dqblk(&qup->qv[QUOTA_LIMIT_BLOCK],
311 			   &qup->qv[QUOTA_LIMIT_FILE],
312 			   &dqblk);
313 	assert((qup->flags & DEFAULT) == 0);
314 
315 	if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
316 		warnx("open `%s'", qup->qfname);
317 	} else {
318 		(void)lseek(fd,
319 		    (off_t)(id * (long)sizeof (struct dqblk)),
320 		    SEEK_SET);
321 		if (write(fd, &dqblk, sizeof (struct dqblk)) !=
322 		    sizeof (struct dqblk))
323 			warnx("writing `%s'", qup->qfname);
324 		close(fd);
325 	}
326 }
327 
328 static struct quotause *
329 getprivs1(long id, int idtype, const char *filesys)
330 {
331 	struct fstab *fs;
332 	char qfpathname[MAXPATHLEN], xbuf[MAXPATHLEN];
333 	const char *fsspec;
334 	struct quotause *qup;
335 	struct dqblk dqblk;
336 	int fd;
337 
338 	setfsent();
339 	while ((fs = getfsent()) != NULL) {
340 		if (strcmp(fs->fs_vfstype, "ffs"))
341 			continue;
342 		fsspec = getfsspecname(xbuf, sizeof(xbuf), fs->fs_spec);
343 		if (fsspec == NULL) {
344 			warn("%s", xbuf);
345 			continue;
346 		}
347 		if (strcmp(fsspec, filesys) == 0 ||
348 		    strcmp(fs->fs_file, filesys) == 0)
349 			break;
350 	}
351 	if (fs == NULL)
352 		return NULL;
353 
354 	if (!hasquota(qfpathname, sizeof(qfpathname), fs,
355 	    quota_idtype_to_ufs(idtype)))
356 		return NULL;
357 
358 	qup = quotause_create();
359 	strcpy(qup->fsname, fs->fs_file);
360 	if ((fd = open(qfpathname, O_RDONLY)) < 0) {
361 		fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
362 		if (fd < 0 && errno != ENOENT) {
363 			warnx("open `%s'", qfpathname);
364 			quotause_destroy(qup);
365 			return NULL;
366 		}
367 		warnx("Creating quota file %s", qfpathname);
368 		sleep(3);
369 		(void)fchown(fd, getuid(),
370 		    getidbyname(quotagroup, QUOTA_CLASS_GROUP));
371 		(void)fchmod(fd, 0640);
372 	}
373 	(void)lseek(fd, (off_t)(id * sizeof(struct dqblk)),
374 	    SEEK_SET);
375 	switch (read(fd, &dqblk, sizeof(struct dqblk))) {
376 	case 0:			/* EOF */
377 		/*
378 		 * Convert implicit 0 quota (EOF)
379 		 * into an explicit one (zero'ed dqblk)
380 		 */
381 		memset(&dqblk, 0, sizeof(struct dqblk));
382 		break;
383 
384 	case sizeof(struct dqblk):	/* OK */
385 		break;
386 
387 	default:		/* ERROR */
388 		warn("read error in `%s'", qfpathname);
389 		close(fd);
390 		quotause_destroy(qup);
391 		return NULL;
392 	}
393 	close(fd);
394 	qup->qfname = qfpathname;
395 	endfsent();
396 	dqblk_to_quotavals(&dqblk,
397 			   &qup->qv[QUOTA_LIMIT_BLOCK],
398 			   &qup->qv[QUOTA_LIMIT_FILE]);
399 	return qup;
400 }
401 #endif
402 
403 ////////////////////////////////////////////////////////////
404 // generic quota interface
405 
406 static int
407 dogetprivs2(struct quotahandle *qh, int idtype, id_t id, int defaultq,
408 	    int objtype, struct quotause *qup)
409 {
410 	struct quotakey qk;
411 
412 	qk.qk_idtype = idtype;
413 	qk.qk_id = defaultq ? QUOTA_DEFAULTID : id;
414 	qk.qk_objtype = objtype;
415 	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
416 		/* succeeded */
417 		qup->source[objtype] = SRC_QUOTA;
418 		return 0;
419 	}
420 	if (errno != ENOENT) {
421 		/* serious failure */
422 		return -1;
423 	}
424 
425 	/* no entry, get default entry */
426 	qk.qk_id = QUOTA_DEFAULTID;
427 	if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) {
428 		/* succeeded */
429 		qup->source[objtype] = SRC_DEFAULT;
430 		return 0;
431 	}
432 	if (errno != ENOENT) {
433 		return -1;
434 	}
435 
436 	/* use a zeroed-out entry */
437 	quotaval_clear(&qup->qv[objtype]);
438 	qup->source[objtype] = SRC_CLEAR;
439 	return 0;
440 }
441 
442 static struct quotause *
443 getprivs2(long id, int idtype, const char *filesys, int defaultq,
444 	  char **idtypename_p)
445 {
446 	struct quotause *qup;
447 	struct quotahandle *qh;
448 	const char *impl;
449 	unsigned restrictions;
450 	const char *idtypename;
451 	int serrno;
452 
453 	qup = quotause_create();
454 	strcpy(qup->fsname, filesys);
455 	if (defaultq)
456 		qup->isdefault = 1;
457 
458 	qh = quota_open(filesys);
459 	if (qh == NULL) {
460 		serrno = errno;
461 		quotause_destroy(qup);
462 		errno = serrno;
463 		return NULL;
464 	}
465 
466 	impl = quota_getimplname(qh);
467 	if (impl == NULL) {
468 		impl = "???";
469 	}
470 	strlcpy(qup->implementation, impl, sizeof(qup->implementation));
471 
472 	restrictions = quota_getrestrictions(qh);
473 	if ((restrictions & QUOTA_RESTRICT_UNIFORMGRACE) == 0) {
474 		qup->xgrace = 1;
475 	}
476 
477 	if (*idtypename_p == NULL) {
478 		idtypename = quota_idtype_getname(qh, idtype);
479 		*idtypename_p = strdup(idtypename);
480 		if (*idtypename_p == NULL) {
481 			errx(1, "Out of memory");
482 		}
483 	}
484 
485 	if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_BLOCKS, qup)) {
486 		serrno = errno;
487 		quota_close(qh);
488 		quotause_destroy(qup);
489 		errno = serrno;
490 		return NULL;
491 	}
492 
493 	if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_FILES, qup)) {
494 		serrno = errno;
495 		quota_close(qh);
496 		quotause_destroy(qup);
497 		errno = serrno;
498 		return NULL;
499 	}
500 
501 	quota_close(qh);
502 
503 	return qup;
504 }
505 
506 static void
507 putprivs2(uint32_t id, int idtype, struct quotause *qup)
508 {
509 	struct quotahandle *qh;
510 	struct quotakey qk;
511 	char idname[32];
512 
513 	if (qup->isdefault) {
514 		snprintf(idname, sizeof(idname), "%s default",
515 			 idtype == QUOTA_IDTYPE_USER ? "user" : "group");
516 		id = QUOTA_DEFAULTID;
517 	} else {
518 		snprintf(idname, sizeof(idname), "%s %u",
519 			 idtype == QUOTA_IDTYPE_USER ? "uid" : "gid", id);
520 	}
521 
522 	qh = quota_open(qup->fsname);
523 	if (qh == NULL) {
524 		err(1, "%s: quota_open", qup->fsname);
525 	}
526 
527 	if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
528 		qk.qk_idtype = idtype;
529 		qk.qk_id = id;
530 		qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
531 		if (quota_put(qh, &qk, &qup->qv[QO_BLK])) {
532 			err(1, "%s: quota_put (%s blocks)", qup->fsname,
533 			    idname);
534 		}
535 	}
536 
537 	if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
538 		qk.qk_idtype = idtype;
539 		qk.qk_id = id;
540 		qk.qk_objtype = QUOTA_OBJTYPE_FILES;
541 		if (quota_put(qh, &qk, &qup->qv[QO_FL])) {
542 			err(1, "%s: quota_put (%s files)", qup->fsname,
543 			    idname);
544 		}
545 	}
546 
547 	quota_close(qh);
548 }
549 
550 ////////////////////////////////////////////////////////////
551 // quota format switch
552 
553 /*
554  * Collect the requested quota information.
555  */
556 static struct quotalist *
557 getprivs(long id, int defaultq, int idtype, const char *filesys)
558 {
559 	struct statvfs *fst;
560 	int nfst, i;
561 	struct quotalist *qlist;
562 	struct quotause *qup;
563 	int seenany = 0;
564 
565 	qlist = quotalist_create();
566 
567 	nfst = getmntinfo(&fst, MNT_WAIT);
568 	if (nfst == 0)
569 		errx(1, "no filesystems mounted!");
570 
571 	for (i = 0; i < nfst; i++) {
572 		if ((fst[i].f_flag & ST_QUOTA) == 0)
573 			continue;
574 		seenany = 1;
575 		if (filesys &&
576 		    strcmp(fst[i].f_mntonname, filesys) != 0 &&
577 		    strcmp(fst[i].f_mntfromname, filesys) != 0)
578 			continue;
579 		qup = getprivs2(id, idtype, fst[i].f_mntonname, defaultq,
580 				&qlist->idtypename);
581 		if (qup == NULL) {
582 			/* XXX the atf tests demand failing silently */
583 			/*warn("Reading quotas failed for id %ld", id);*/
584 			continue;
585 		}
586 
587 		quotalist_append(qlist, qup);
588 	}
589 
590 	if (!seenany) {
591 		errx(1, "No mounted filesystems have quota support");
592 	}
593 
594 #if 0
595 	if (filesys && quotalist_empty(qlist)) {
596 		if (defaultq)
597 			errx(1, "no default quota for version 1");
598 		/* if we get there, filesys is not mounted. try the old way */
599 		qup = getprivs1(id, idtype, filesys);
600 		if (qup == NULL) {
601 			warnx("getprivs1 failed");
602 			return qlist;
603 		}
604 		quotalist_append(qlist, qup);
605 	}
606 #endif
607 	return qlist;
608 }
609 
610 /*
611  * Store the requested quota information.
612  */
613 static void
614 putprivs(uint32_t id, int idtype, struct quotalist *qlist)
615 {
616 	struct quotause *qup;
617 
618         for (qup = qlist->head; qup; qup = qup->next) {
619 #if 0
620 		if (qup->qfname != NULL)
621 			putprivs1(id, idtype, qup);
622 		else
623 #endif
624 			putprivs2(id, idtype, qup);
625 	}
626 }
627 
628 static void
629 clearpriv(int argc, char **argv, const char *filesys, int idtype)
630 {
631 	struct statvfs *fst;
632 	int nfst, i;
633 	int id;
634 	id_t *ids;
635 	unsigned nids, maxids, j;
636 	struct quotahandle *qh;
637 	struct quotakey qk;
638 	char idname[32];
639 
640 	maxids = 4;
641 	nids = 0;
642 	ids = malloc(maxids * sizeof(ids[0]));
643 	if (ids == NULL) {
644 		err(1, "malloc");
645 	}
646 
647 	for ( ; argc > 0; argc--, argv++) {
648 		if ((id = getidbyname(*argv, idtype)) == -1)
649 			continue;
650 
651 		if (nids + 1 > maxids) {
652 			maxids *= 2;
653 			ids = realloc(ids, maxids * sizeof(ids[0]));
654 			if (ids == NULL) {
655 				err(1, "realloc");
656 			}
657 		}
658 		ids[nids++] = id;
659 	}
660 
661 	/* now loop over quota-enabled filesystems */
662 	nfst = getmntinfo(&fst, MNT_WAIT);
663 	if (nfst == 0)
664 		errx(1, "no filesystems mounted!");
665 
666 	for (i = 0; i < nfst; i++) {
667 		if ((fst[i].f_flag & ST_QUOTA) == 0)
668 			continue;
669 		if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
670 		    strcmp(fst[i].f_mntfromname, filesys) != 0)
671 			continue;
672 
673 		qh = quota_open(fst[i].f_mntonname);
674 		if (qh == NULL) {
675 			err(1, "%s: quota_open", fst[i].f_mntonname);
676 		}
677 
678 		for (j = 0; j < nids; j++) {
679 			snprintf(idname, sizeof(idname), "%s %u",
680 				 idtype == QUOTA_IDTYPE_USER ?
681 				 "uid" : "gid", ids[j]);
682 
683 			qk.qk_idtype = idtype;
684 			qk.qk_id = ids[j];
685 			qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
686 			if (quota_delete(qh, &qk)) {
687 				err(1, "%s: quota_delete (%s blocks)",
688 				    fst[i].f_mntonname, idname);
689 			}
690 
691 			qk.qk_idtype = idtype;
692 			qk.qk_id = ids[j];
693 			qk.qk_objtype = QUOTA_OBJTYPE_FILES;
694 			if (quota_delete(qh, &qk)) {
695 				if (errno == ENOENT) {
696  					/*
697 					 * XXX ignore this case; due
698 					 * to a weakness in the quota
699 					 * proplib interface it can
700 					 * appear spuriously.
701 					 */
702 				} else {
703 					err(1, "%s: quota_delete (%s files)",
704 					    fst[i].f_mntonname, idname);
705 				}
706 			}
707 		}
708 
709 		quota_close(qh);
710 	}
711 
712 	free(ids);
713 }
714 
715 ////////////////////////////////////////////////////////////
716 // editor
717 
718 /*
719  * Take a list of privileges and get it edited.
720  */
721 static int
722 editit(const char *ltmpfile)
723 {
724 	pid_t pid;
725 	int lst;
726 	char p[MAX_TMPSTR];
727 	const char *ed;
728 	sigset_t s, os;
729 
730 	sigemptyset(&s);
731 	sigaddset(&s, SIGINT);
732 	sigaddset(&s, SIGQUIT);
733 	sigaddset(&s, SIGHUP);
734 	if (sigprocmask(SIG_BLOCK, &s, &os) == -1)
735 		err(1, "sigprocmask");
736 top:
737 	switch ((pid = fork())) {
738 	case -1:
739 		if (errno == EPROCLIM) {
740 			warnx("You have too many processes");
741 			return 0;
742 		}
743 		if (errno == EAGAIN) {
744 			sleep(1);
745 			goto top;
746 		}
747 		warn("fork");
748 		return 0;
749 	case 0:
750 		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
751 			err(1, "sigprocmask");
752 		setgid(getgid());
753 		setuid(getuid());
754 		if ((ed = getenv("EDITOR")) == (char *)0)
755 			ed = _PATH_VI;
756 		if (strlen(ed) + strlen(ltmpfile) + 2 >= MAX_TMPSTR) {
757 			errx(1, "%s", "editor or filename too long");
758 		}
759 		snprintf(p, sizeof(p), "%s %s", ed, ltmpfile);
760 		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
761 		err(1, "%s", ed);
762 	default:
763 		if (waitpid(pid, &lst, 0) == -1)
764 			err(1, "waitpid");
765 		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
766 			err(1, "sigprocmask");
767 		if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0)
768 			return 0;
769 		return 1;
770 	}
771 }
772 
773 /*
774  * Convert a quotause list to an ASCII file.
775  */
776 static int
777 writeprivs(struct quotalist *qlist, int outfd, const char *name,
778     int idtype, const char *idtypename)
779 {
780 	struct quotause *qup;
781 	FILE *fd;
782 	char b0[32], b1[32], b2[32], b3[32];
783 	const char *comm;
784 
785 	(void)ftruncate(outfd, 0);
786 	(void)lseek(outfd, (off_t)0, SEEK_SET);
787 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
788 		errx(1, "fdopen");
789 	if (name == NULL) {
790 		fprintf(fd, "Default %s quotas:\n", idtypename);
791 	} else {
792 		fprintf(fd, "Quotas for %s %s:\n", idtypename, name);
793 	}
794 	for (qup = qlist->head; qup; qup = qup->next) {
795 		struct quotaval *q = qup->qv;
796 
797 		fprintf(fd, "%s (%s):\n", qup->fsname, qup->implementation);
798 
799 		comm = source_is_real(qup->source[QO_BLK]) ? "" : "#";
800 		fprintf(fd, "\tblocks:%s\n",
801 			Hflag ? "" : " (sizes in 1K-blocks)");
802 		fprintf(fd, "\t\t%susage: %s\n", comm,
803 			intprt(b1, 21, q[QO_BLK].qv_usage,
804 			       HN_NOSPACE | HN_B, Hflag));
805 		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
806 			intprt(b2, 21, q[QO_BLK].qv_softlimit,
807 			       HN_NOSPACE | HN_B, Hflag),
808 			intprt(b3, 21, q[QO_BLK].qv_hardlimit,
809 			       HN_NOSPACE | HN_B, Hflag));
810 		if (qup->xgrace || qup->isdefault) {
811 			fprintf(fd, "\t\t%sgrace: %s\n", comm,
812 				timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
813 		}
814 
815 		comm = source_is_real(qup->source[QO_FL]) ? "" : "#";
816 		fprintf(fd, "\tinodes:\n");
817 		fprintf(fd, "\t\t%susage: %s\n", comm,
818 			intprt(b1, 21, q[QO_FL].qv_usage,
819 			       HN_NOSPACE, Hflag));
820 		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
821 			intprt(b2, 21, q[QO_FL].qv_softlimit,
822 			       HN_NOSPACE, Hflag),
823 			intprt(b3, 21, q[QO_FL].qv_hardlimit,
824 			       HN_NOSPACE, Hflag));
825 		if (qup->xgrace || qup->isdefault) {
826 			fprintf(fd, "\t\t%sgrace: %s\n", comm,
827 				timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
828 		}
829 	}
830 	fclose(fd);
831 	return 1;
832 }
833 
834 /*
835  * Merge changes to an ASCII file into a quotause list.
836  */
837 static int
838 readprivs(struct quotalist *qlist, int infd, int dflag)
839 {
840 	FILE *fd;			/* file */
841 	unsigned lineno;		/* line number in file */
842 	char line[BUFSIZ];		/* input buffer */
843 	size_t len;			/* length of input buffer */
844 	bool seenheader;		/* true if past the file header */
845 	struct quotause *qup;		/* current filesystem */
846 	bool haveobjtype;		/* true if objtype is valid */
847 	unsigned objtype;		/* current object type */
848 	int objtypeflags;		/* humanize flags */
849 	/* XXX make following const later (requires non-local cleanup) */
850 	/*const*/ char *text, *text2, *t; /* scratch values */
851 	char b0[32], b1[32];
852 	uint64_t soft, hard, current;
853 	time_t grace;
854 	struct quotaval *qv;
855 
856 	lineno = 0;
857 	seenheader = false;
858 	qup = NULL;
859 	haveobjtype = false;
860 	objtype = QUOTA_OBJTYPE_BLOCKS; /* for gcc 4.5 */
861 	objtypeflags = HN_B;
862 
863 	(void)lseek(infd, (off_t)0, SEEK_SET);
864 	fd = fdopen(dup(infd), "r");
865 	if (fd == NULL) {
866 		warn("Can't re-read temp file");
867 		return -1;
868 	}
869 
870 	/*
871 	 * Read back the same format we wrote.
872 	 */
873 
874 	while (fgets(line, sizeof(line), fd)) {
875 		lineno++;
876 		if (!seenheader) {
877 			if ((!strncmp(line, "Default ", 8) && dflag) ||
878 			    (!strncmp(line, "Quotas for ", 11) && !dflag)) {
879 				/* ok. */
880 				seenheader = 1;
881 				continue;
882 			}
883 			warnx("Header line missing");
884 			goto fail;
885 		}
886 		len = strlen(line);
887 		if (len == 0) {
888 			/* ? */
889 			continue;
890 		}
891 		if (line[len - 1] != '\n') {
892 			warnx("Line %u too long", lineno);
893 			goto fail;
894 		}
895 		line[--len] = '\0';
896 		if (line[len - 1] == '\r') {
897 			line[--len] = '\0';
898 		}
899 		if (len == 0) {
900 			/* blank line */
901 			continue;
902 		}
903 
904 		/*
905 		 * If the line has:     it is:
906                  *      two tabs        values
907 		 *      one tab         the next objtype
908 		 *      no tabs         the next filesystem
909 		 */
910 		if (line[0] == '\t' && line[1] == '\t') {
911 			if (qup == NULL) {
912 				warnx("Line %u: values with no filesystem",
913 				      lineno);
914 				goto fail;
915 			}
916 			if (!haveobjtype) {
917 				warnx("Line %u: values with no object type",
918 				      lineno);
919 				goto fail;
920 			}
921 			qv = &qup->qv[objtype];
922 
923 			text = line + 2;
924 			if (*text == '#') {
925 				/* commented out; ignore */
926 				continue;
927 			}
928 			else if (!strncmp(text, "usage:", 6)) {
929 
930 				/* usage: %llu */
931 				text += 6;
932 				t = skipws(text);
933 				if (intrd(t, &current, objtypeflags) != 0) {
934 					warnx("Line %u: Bad number %s",
935 					      lineno, t);
936 					goto fail;
937 				}
938 
939 				/*
940 				 * Because the humanization can lead
941 				 * to roundoff, check if the two
942 				 * values produce the same humanized
943 				 * string, rather than if they're the
944 				 * same number. Sigh.
945 				 */
946 				intprt(b0, 21, current,
947 				       HN_NOSPACE | objtypeflags, Hflag);
948 				intprt(b1, 21, qv->qv_usage,
949 				       HN_NOSPACE | objtypeflags, Hflag);
950 				if (strcmp(b0, b1)) {
951 					warnx("Line %u: cannot change usage",
952 					      lineno);
953 				}
954 				continue;
955 
956 			} else if (!strncmp(text, "limits:", 7)) {
957 
958 				/* limits: soft %llu, hard %llu */
959 				text += 7;
960 				text2 = strchr(text, ',');
961 				if (text2 == NULL) {
962 					warnx("Line %u: expected comma",
963 					      lineno);
964 					goto fail;
965 				}
966 				*text2 = '\0';
967 				text2++;
968 
969 				t = skipws(text);
970 				t = skipword(t);
971 				t = skipws(t);
972 				if (intrd(t, &soft, objtypeflags) != 0) {
973 					warnx("Line %u: Bad number %s",
974 					      lineno, t);
975 					goto fail;
976 				}
977 				t = skipws(text2);
978 				t = skipword(t);
979 				t = skipws(t);
980 				if (intrd(t, &hard, objtypeflags) != 0) {
981 					warnx("Line %u: Bad number %s",
982 					      lineno, t);
983 					goto fail;
984 				}
985 
986 				/*
987 				 * Cause time limit to be reset when the quota
988 				 * is next used if previously had no soft limit
989 				 * or were under it, but now have a soft limit
990 				 * and are over it.
991 				 */
992 				if (qv->qv_usage && qv->qv_usage >= soft &&
993 				    (qv->qv_softlimit == 0 ||
994 				     qv->qv_usage < qv->qv_softlimit)) {
995 					qv->qv_expiretime = 0;
996 				}
997 				if (soft != qv->qv_softlimit ||
998 				    hard != qv->qv_hardlimit) {
999 					qv->qv_softlimit = soft;
1000 					qv->qv_hardlimit = hard;
1001 					qup->source[objtype] = SRC_EDITED;
1002 				}
1003 				qup->found = 1;
1004 
1005 			} else if (!strncmp(text, "grace:", 6)) {
1006 
1007 				text += 6;
1008 				/* grace: %llu */
1009 				t = skipws(text);
1010 				if (timeprd(t, &grace) != 0) {
1011 					warnx("Line %u: Bad number %s",
1012 					      lineno, t);
1013 					goto fail;
1014 				}
1015 				if (qup->isdefault || qup->xgrace) {
1016 					if (grace != qv->qv_grace) {
1017 						qv->qv_grace = grace;
1018 						qup->source[objtype] =
1019 							SRC_EDITED;
1020 					}
1021 					qup->found = 1;
1022 				} else {
1023 					warnx("Line %u: Cannot set individual "
1024 					      "grace time on this filesystem",
1025 					      lineno);
1026 					goto fail;
1027 				}
1028 
1029 			} else {
1030 				warnx("Line %u: Unknown/unexpected value line",
1031 				      lineno);
1032 				goto fail;
1033 			}
1034 		} else if (line[0] == '\t') {
1035 			text = line + 1;
1036 			if (*text == '#') {
1037 				/* commented out; ignore */
1038 				continue;
1039 			}
1040 			else if (!strncmp(text, "blocks:", 7)) {
1041 				objtype = QUOTA_OBJTYPE_BLOCKS;
1042 				objtypeflags = HN_B;
1043 				haveobjtype = true;
1044 			} else if (!strncmp(text, "inodes:", 7)) {
1045 				objtype = QUOTA_OBJTYPE_FILES;
1046 				objtypeflags = 0;
1047 				haveobjtype = true;
1048 			} else {
1049 				warnx("Line %u: Unknown/unexpected object "
1050 				      "type (%s)", lineno, text);
1051 				goto fail;
1052 			}
1053 		} else {
1054 			t = strchr(line, ' ');
1055 			if (t == NULL) {
1056 				t = strchr(line, ':');
1057 				if (t == NULL) {
1058 					t = line + len;
1059 				}
1060 			}
1061 			*t = '\0';
1062 
1063 			if (*line == '#') {
1064 				/* commented out; ignore */
1065 				continue;
1066 			}
1067 
1068 			for (qup = qlist->head; qup; qup = qup->next) {
1069 				if (!strcmp(line, qup->fsname))
1070 					break;
1071 			}
1072 			if (qup == NULL) {
1073 				warnx("Line %u: Filesystem %s invalid or has "
1074 				      "no quota support", lineno, line);
1075 				goto fail;
1076 			}
1077 			haveobjtype = false;
1078 		}
1079 	}
1080 
1081 	fclose(fd);
1082 
1083 	/*
1084 	 * Disable quotas for any filesystems that we didn't see,
1085 	 * because they must have been deleted in the editor.
1086 	 *
1087 	 * XXX this should be improved so it results in
1088 	 * quota_delete(), not just writing out a blank quotaval.
1089 	 */
1090 	for (qup = qlist->head; qup; qup = qup->next) {
1091 		if (qup->found) {
1092 			qup->found = 0;
1093 			continue;
1094 		}
1095 
1096 		if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
1097 			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_BLOCKS]);
1098 			qup->source[QUOTA_OBJTYPE_BLOCKS] = SRC_EDITED;
1099 		}
1100 
1101 		if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
1102 			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_FILES]);
1103 			qup->source[QUOTA_OBJTYPE_FILES] = SRC_EDITED;
1104 		}
1105 	}
1106 	return 0;
1107 
1108 fail:
1109 	sleep(3);
1110 	fclose(fd);
1111 	return -1;
1112 }
1113 
1114 ////////////////////////////////////////////////////////////
1115 // actions
1116 
1117 static void
1118 replicate(const char *fs, int idtype, const char *protoname,
1119 	  char **names, int numnames)
1120 {
1121 	long protoid, id;
1122 	struct quotalist *protoprivs;
1123 	struct quotause *qup;
1124 	int i;
1125 
1126 	if ((protoid = getidbyname(protoname, idtype)) == -1)
1127 		exit(1);
1128 	protoprivs = getprivs(protoid, 0, idtype, fs);
1129 	for (qup = protoprivs->head; qup; qup = qup->next) {
1130 		qup->qv[QO_BLK].qv_expiretime = 0;
1131 		qup->qv[QO_FL].qv_expiretime = 0;
1132 		qup->source[QO_BLK] = SRC_EDITED;
1133 		qup->source[QO_FL] = SRC_EDITED;
1134 	}
1135 	for (i=0; i<numnames; i++) {
1136 		id = getidbyname(names[i], idtype);
1137 		if (id == -1)
1138 			continue;
1139 		putprivs(id, idtype, protoprivs);
1140 	}
1141 	/* XXX */
1142 	/* quotalist_destroy(protoprivs); */
1143 }
1144 
1145 static void
1146 assign(const char *fs, int idtype,
1147        char *soft, char *hard, char *grace,
1148        char **names, int numnames)
1149 {
1150 	struct quotalist *curprivs;
1151 	struct quotause *lqup;
1152 	u_int64_t softb, hardb, softi, hardi;
1153 	time_t  graceb, gracei;
1154 	char *str;
1155 	long id;
1156 	int dflag;
1157 	int i;
1158 
1159 	if (soft) {
1160 		str = strsep(&soft, "/");
1161 		if (str[0] == '\0' || soft == NULL || soft[0] == '\0')
1162 			usage();
1163 
1164 		if (intrd(str, &softb, HN_B) != 0)
1165 			errx(1, "%s: bad number", str);
1166 		if (intrd(soft, &softi, 0) != 0)
1167 			errx(1, "%s: bad number", soft);
1168 	}
1169 	if (hard) {
1170 		str = strsep(&hard, "/");
1171 		if (str[0] == '\0' || hard == NULL || hard[0] == '\0')
1172 			usage();
1173 
1174 		if (intrd(str, &hardb, HN_B) != 0)
1175 			errx(1, "%s: bad number", str);
1176 		if (intrd(hard, &hardi, 0) != 0)
1177 			errx(1, "%s: bad number", hard);
1178 	}
1179 	if (grace) {
1180 		str = strsep(&grace, "/");
1181 		if (str[0] == '\0' || grace == NULL || grace[0] == '\0')
1182 			usage();
1183 
1184 		if (timeprd(str, &graceb) != 0)
1185 			errx(1, "%s: bad number", str);
1186 		if (timeprd(grace, &gracei) != 0)
1187 			errx(1, "%s: bad number", grace);
1188 	}
1189 	for (i=0; i<numnames; i++) {
1190 		if (names[i] == NULL) {
1191 			id = 0;
1192 			dflag = 1;
1193 		} else {
1194 			id = getidbyname(names[i], idtype);
1195 			if (id == -1)
1196 				continue;
1197 			dflag = 0;
1198 		}
1199 
1200 		curprivs = getprivs(id, dflag, idtype, fs);
1201 		for (lqup = curprivs->head; lqup; lqup = lqup->next) {
1202 			struct quotaval *q = lqup->qv;
1203 			if (soft) {
1204 				if (!dflag && softb &&
1205 				    q[QO_BLK].qv_usage >= softb &&
1206 				    (q[QO_BLK].qv_softlimit == 0 ||
1207 				     q[QO_BLK].qv_usage <
1208 				     q[QO_BLK].qv_softlimit))
1209 					q[QO_BLK].qv_expiretime = 0;
1210 				if (!dflag && softi &&
1211 				    q[QO_FL].qv_usage >= softb &&
1212 				    (q[QO_FL].qv_softlimit == 0 ||
1213 				     q[QO_FL].qv_usage <
1214 				     q[QO_FL].qv_softlimit))
1215 					q[QO_FL].qv_expiretime = 0;
1216 				q[QO_BLK].qv_softlimit = softb;
1217 				q[QO_FL].qv_softlimit = softi;
1218 			}
1219 			if (hard) {
1220 				q[QO_BLK].qv_hardlimit = hardb;
1221 				q[QO_FL].qv_hardlimit = hardi;
1222 			}
1223 			if (grace) {
1224 				q[QO_BLK].qv_grace = graceb;
1225 				q[QO_FL].qv_grace = gracei;
1226 			}
1227 			lqup->source[QO_BLK] = SRC_EDITED;
1228 			lqup->source[QO_FL] = SRC_EDITED;
1229 		}
1230 		putprivs(id, idtype, curprivs);
1231 		quotalist_destroy(curprivs);
1232 	}
1233 }
1234 
1235 static void
1236 clear(const char *fs, int idtype, char **names, int numnames)
1237 {
1238 	clearpriv(numnames, names, fs, idtype);
1239 }
1240 
1241 static void
1242 editone(const char *fs, int idtype, const char *name,
1243 	int tmpfd, const char *tmppath)
1244 {
1245 	struct quotalist *curprivs;
1246 	long id;
1247 	int dflag;
1248 
1249 	if (name == NULL) {
1250 		id = 0;
1251 		dflag = 1;
1252 	} else {
1253 		id = getidbyname(name, idtype);
1254 		if (id == -1)
1255 			return;
1256 		dflag = 0;
1257 	}
1258 	curprivs = getprivs(id, dflag, idtype, fs);
1259 
1260 	if (writeprivs(curprivs, tmpfd, name, idtype,
1261 		       curprivs->idtypename) == 0)
1262 		goto fail;
1263 
1264 	if (editit(tmppath) == 0)
1265 		goto fail;
1266 
1267 	if (readprivs(curprivs, tmpfd, dflag) < 0)
1268 		goto fail;
1269 
1270 	putprivs(id, idtype, curprivs);
1271 fail:
1272 	quotalist_destroy(curprivs);
1273 }
1274 
1275 static void
1276 edit(const char *fs, int idtype, char **names, int numnames)
1277 {
1278 	char tmppath[] = _PATH_TMPFILE;
1279 	int tmpfd, i;
1280 
1281 	tmpfd = mkstemp(tmppath);
1282 	fchown(tmpfd, getuid(), getgid());
1283 
1284 	for (i=0; i<numnames; i++) {
1285 		editone(fs, idtype, names[i], tmpfd, tmppath);
1286 	}
1287 
1288 	close(tmpfd);
1289 	unlink(tmppath);
1290 }
1291 
1292 ////////////////////////////////////////////////////////////
1293 // main
1294 
1295 static void
1296 usage(void)
1297 {
1298 	const char *p = getprogname();
1299 	fprintf(stderr,
1300 	    "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] "
1301 		"-d | <username> ...\n"
1302 	    "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] "
1303 		"-d | <groupname> ...\n"
1304 	    "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1305 		"-d | <username> ...\n"
1306 	    "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
1307 		"-d | <groupname> ...\n"
1308 	    "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n"
1309 	    "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n",
1310 	    p, p, p, p, p, p);
1311 	exit(1);
1312 }
1313 
1314 int
1315 main(int argc, char *argv[])
1316 {
1317 	int idtype;
1318 	char *protoname;
1319 	char *soft = NULL, *hard = NULL, *grace = NULL;
1320 	char *fs = NULL;
1321 	int ch;
1322 	int pflag = 0;
1323 	int cflag = 0;
1324 	int dflag = 0;
1325 
1326 	if (argc < 2)
1327 		usage();
1328 	if (getuid())
1329 		errx(1, "permission denied");
1330 	protoname = NULL;
1331 	idtype = QUOTA_IDTYPE_USER;
1332 	while ((ch = getopt(argc, argv, "Hcdugp:s:h:t:f:")) != -1) {
1333 		switch(ch) {
1334 		case 'H':
1335 			Hflag++;
1336 			break;
1337 		case 'c':
1338 			cflag++;
1339 			break;
1340 		case 'd':
1341 			dflag++;
1342 			break;
1343 		case 'p':
1344 			protoname = optarg;
1345 			pflag++;
1346 			break;
1347 		case 'g':
1348 			idtype = QUOTA_IDTYPE_GROUP;
1349 			break;
1350 		case 'u':
1351 			idtype = QUOTA_IDTYPE_USER;
1352 			break;
1353 		case 's':
1354 			soft = optarg;
1355 			break;
1356 		case 'h':
1357 			hard = optarg;
1358 			break;
1359 		case 't':
1360 			grace = optarg;
1361 			break;
1362 		case 'f':
1363 			fs = optarg;
1364 			break;
1365 		default:
1366 			usage();
1367 		}
1368 	}
1369 	argc -= optind;
1370 	argv += optind;
1371 
1372 	if (pflag) {
1373 		if (soft || hard || grace || dflag || cflag)
1374 			usage();
1375 		replicate(fs, idtype, protoname, argv, argc);
1376 	} else if (soft || hard || grace) {
1377 		if (cflag)
1378 			usage();
1379 		if (dflag) {
1380 			/* use argv[argc], which is null, to mean 'default' */
1381 			argc++;
1382 		}
1383 		assign(fs, idtype, soft, hard, grace, argv, argc);
1384 	} else if (cflag) {
1385 		if (dflag)
1386 			usage();
1387 		clear(fs, idtype, argv, argc);
1388 	} else {
1389 		if (dflag) {
1390 			/* use argv[argc], which is null, to mean 'default' */
1391 			argc++;
1392 		}
1393 		edit(fs, idtype, argv, argc);
1394 	}
1395 	return 0;
1396 }
1397