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