Home | History | Annotate | Line # | Download | only in edquota
      1 /*      $NetBSD: edquota.c,v 1.54 2023/08/01 08:47:25 mrg 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.54 2023/08/01 08:47:25 mrg 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 = NULL;
    643 	if (reallocarr(&ids, maxids, sizeof(ids[0])) != 0) {
    644 		err(1, "reallocarr");
    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 			if (reallocarr(&ids, maxids, sizeof(ids[0])) != 0) {
    654 				err(1, "reallocarr");
    655 			}
    656 		}
    657 		ids[nids++] = id;
    658 	}
    659 
    660 	/* now loop over quota-enabled filesystems */
    661 	nfst = getmntinfo(&fst, MNT_WAIT);
    662 	if (nfst == 0)
    663 		errx(1, "no filesystems mounted!");
    664 
    665 	for (i = 0; i < nfst; i++) {
    666 		if ((fst[i].f_flag & ST_QUOTA) == 0)
    667 			continue;
    668 		if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 &&
    669 		    strcmp(fst[i].f_mntfromname, filesys) != 0)
    670 			continue;
    671 
    672 		qh = quota_open(fst[i].f_mntonname);
    673 		if (qh == NULL) {
    674 			err(1, "%s: quota_open", fst[i].f_mntonname);
    675 		}
    676 
    677 		for (j = 0; j < nids; j++) {
    678 			snprintf(idname, sizeof(idname), "%s %u",
    679 				 idtype == QUOTA_IDTYPE_USER ?
    680 				 "uid" : "gid", ids[j]);
    681 
    682 			qk.qk_idtype = idtype;
    683 			qk.qk_id = ids[j];
    684 			qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS;
    685 			if (quota_delete(qh, &qk)) {
    686 				err(1, "%s: quota_delete (%s blocks)",
    687 				    fst[i].f_mntonname, idname);
    688 			}
    689 
    690 			qk.qk_idtype = idtype;
    691 			qk.qk_id = ids[j];
    692 			qk.qk_objtype = QUOTA_OBJTYPE_FILES;
    693 			if (quota_delete(qh, &qk)) {
    694 				if (errno == ENOENT) {
    695  					/*
    696 					 * XXX ignore this case; due
    697 					 * to a weakness in the quota
    698 					 * proplib interface it can
    699 					 * appear spuriously.
    700 					 */
    701 				} else {
    702 					err(1, "%s: quota_delete (%s files)",
    703 					    fst[i].f_mntonname, idname);
    704 				}
    705 			}
    706 		}
    707 
    708 		quota_close(qh);
    709 	}
    710 
    711 	free(ids);
    712 }
    713 
    714 ////////////////////////////////////////////////////////////
    715 // editor
    716 
    717 /*
    718  * Take a list of privileges and get it edited.
    719  */
    720 static int
    721 editit(const char *ltmpfile)
    722 {
    723 	pid_t pid;
    724 	int lst;
    725 	char p[MAX_TMPSTR];
    726 	const char *ed;
    727 	sigset_t s, os;
    728 
    729 	sigemptyset(&s);
    730 	sigaddset(&s, SIGINT);
    731 	sigaddset(&s, SIGQUIT);
    732 	sigaddset(&s, SIGHUP);
    733 	if (sigprocmask(SIG_BLOCK, &s, &os) == -1)
    734 		err(1, "sigprocmask");
    735 top:
    736 	switch ((pid = fork())) {
    737 	case -1:
    738 		if (errno == EPROCLIM) {
    739 			warnx("You have too many processes");
    740 			return 0;
    741 		}
    742 		if (errno == EAGAIN) {
    743 			sleep(1);
    744 			goto top;
    745 		}
    746 		warn("fork");
    747 		return 0;
    748 	case 0:
    749 		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
    750 			err(1, "sigprocmask");
    751 		setgid(getgid());
    752 		setuid(getuid());
    753 		if ((ed = getenv("EDITOR")) == (char *)0)
    754 			ed = _PATH_VI;
    755 		if ((size_t)snprintf(p, sizeof(p), "%s %s", ed, ltmpfile) >=
    756 		    sizeof(p)) {
    757 			errx(1, "%s", "editor or filename too long");
    758 		}
    759 		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
    760 		err(1, "%s", ed);
    761 	default:
    762 		if (waitpid(pid, &lst, 0) == -1)
    763 			err(1, "waitpid");
    764 		if (sigprocmask(SIG_SETMASK, &os, NULL) == -1)
    765 			err(1, "sigprocmask");
    766 		if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0)
    767 			return 0;
    768 		return 1;
    769 	}
    770 }
    771 
    772 /*
    773  * Convert a quotause list to an ASCII file.
    774  */
    775 static int
    776 writeprivs(struct quotalist *qlist, int outfd, const char *name,
    777     int idtype, const char *idtypename)
    778 {
    779 	struct quotause *qup;
    780 	FILE *fd;
    781 	char b0[32], b1[32], b2[32], b3[32];
    782 	const char *comm;
    783 
    784 	(void)ftruncate(outfd, 0);
    785 	(void)lseek(outfd, (off_t)0, SEEK_SET);
    786 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
    787 		errx(1, "fdopen");
    788 	if (name == NULL) {
    789 		fprintf(fd, "Default %s quotas:\n", idtypename);
    790 	} else {
    791 		fprintf(fd, "Quotas for %s %s:\n", idtypename, name);
    792 	}
    793 	for (qup = qlist->head; qup; qup = qup->next) {
    794 		struct quotaval *q = qup->qv;
    795 
    796 		fprintf(fd, "%s (%s):\n", qup->fsname, qup->implementation);
    797 
    798 		comm = source_is_real(qup->source[QO_BLK]) ? "" : "#";
    799 		fprintf(fd, "\tblocks:%s\n",
    800 			Hflag ? "" : " (sizes in 1K-blocks)");
    801 		fprintf(fd, "\t\t%susage: %s\n", comm,
    802 			intprt(b1, 21, q[QO_BLK].qv_usage,
    803 			       HN_NOSPACE | HN_B, Hflag));
    804 		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
    805 			intprt(b2, 21, q[QO_BLK].qv_softlimit,
    806 			       HN_NOSPACE | HN_B, Hflag),
    807 			intprt(b3, 21, q[QO_BLK].qv_hardlimit,
    808 			       HN_NOSPACE | HN_B, Hflag));
    809 		if (qup->xgrace || qup->isdefault) {
    810 			fprintf(fd, "\t\t%sgrace: %s\n", comm,
    811 				timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag));
    812 		}
    813 
    814 		comm = source_is_real(qup->source[QO_FL]) ? "" : "#";
    815 		fprintf(fd, "\tinodes:\n");
    816 		fprintf(fd, "\t\t%susage: %s\n", comm,
    817 			intprt(b1, 21, q[QO_FL].qv_usage,
    818 			       HN_NOSPACE, Hflag));
    819 		fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm,
    820 			intprt(b2, 21, q[QO_FL].qv_softlimit,
    821 			       HN_NOSPACE, Hflag),
    822 			intprt(b3, 21, q[QO_FL].qv_hardlimit,
    823 			       HN_NOSPACE, Hflag));
    824 		if (qup->xgrace || qup->isdefault) {
    825 			fprintf(fd, "\t\t%sgrace: %s\n", comm,
    826 				timepprt(b0, 21, q[QO_FL].qv_grace, Hflag));
    827 		}
    828 	}
    829 	fclose(fd);
    830 	return 1;
    831 }
    832 
    833 /*
    834  * Merge changes to an ASCII file into a quotause list.
    835  */
    836 static int
    837 readprivs(struct quotalist *qlist, int infd, int dflag)
    838 {
    839 	FILE *fd;			/* file */
    840 	unsigned lineno;		/* line number in file */
    841 	char line[BUFSIZ];		/* input buffer */
    842 	size_t len;			/* length of input buffer */
    843 	bool seenheader;		/* true if past the file header */
    844 	struct quotause *qup;		/* current filesystem */
    845 	bool haveobjtype;		/* true if objtype is valid */
    846 	unsigned objtype;		/* current object type */
    847 	int objtypeflags;		/* humanize flags */
    848 	/* XXX make following const later (requires non-local cleanup) */
    849 	/*const*/ char *text, *text2, *t; /* scratch values */
    850 	char b0[32], b1[32];
    851 	uint64_t soft, hard, current;
    852 	time_t grace;
    853 	struct quotaval *qv;
    854 
    855 	lineno = 0;
    856 	seenheader = false;
    857 	qup = NULL;
    858 	haveobjtype = false;
    859 	objtype = QUOTA_OBJTYPE_BLOCKS; /* for gcc 4.5 */
    860 	objtypeflags = HN_B;
    861 
    862 	(void)lseek(infd, (off_t)0, SEEK_SET);
    863 	fd = fdopen(dup(infd), "r");
    864 	if (fd == NULL) {
    865 		warn("Can't re-read temp file");
    866 		return -1;
    867 	}
    868 
    869 	/*
    870 	 * Read back the same format we wrote.
    871 	 */
    872 
    873 	while (fgets(line, sizeof(line), fd)) {
    874 		lineno++;
    875 		if (!seenheader) {
    876 			if ((!strncmp(line, "Default ", 8) && dflag) ||
    877 			    (!strncmp(line, "Quotas for ", 11) && !dflag)) {
    878 				/* ok. */
    879 				seenheader = 1;
    880 				continue;
    881 			}
    882 			warnx("Header line missing");
    883 			goto fail;
    884 		}
    885 		len = strlen(line);
    886 		if (len == 0) {
    887 			/* ? */
    888 			continue;
    889 		}
    890 		if (line[len - 1] != '\n') {
    891 			warnx("Line %u too long", lineno);
    892 			goto fail;
    893 		}
    894 		line[--len] = '\0';
    895 		if (line[len - 1] == '\r') {
    896 			line[--len] = '\0';
    897 		}
    898 		if (len == 0) {
    899 			/* blank line */
    900 			continue;
    901 		}
    902 
    903 		/*
    904 		 * If the line has:     it is:
    905                  *      two tabs        values
    906 		 *      one tab         the next objtype
    907 		 *      no tabs         the next filesystem
    908 		 */
    909 		if (line[0] == '\t' && line[1] == '\t') {
    910 			if (qup == NULL) {
    911 				warnx("Line %u: values with no filesystem",
    912 				      lineno);
    913 				goto fail;
    914 			}
    915 			if (!haveobjtype) {
    916 				warnx("Line %u: values with no object type",
    917 				      lineno);
    918 				goto fail;
    919 			}
    920 			qv = &qup->qv[objtype];
    921 
    922 			text = line + 2;
    923 			if (*text == '#') {
    924 				/* commented out; ignore */
    925 				continue;
    926 			}
    927 			else if (!strncmp(text, "usage:", 6)) {
    928 
    929 				/* usage: %llu */
    930 				text += 6;
    931 				t = skipws(text);
    932 				if (intrd(t, &current, objtypeflags) != 0) {
    933 					warnx("Line %u: Bad number %s",
    934 					      lineno, t);
    935 					goto fail;
    936 				}
    937 
    938 				/*
    939 				 * Because the humanization can lead
    940 				 * to roundoff, check if the two
    941 				 * values produce the same humanized
    942 				 * string, rather than if they're the
    943 				 * same number. Sigh.
    944 				 */
    945 				intprt(b0, 21, current,
    946 				       HN_NOSPACE | objtypeflags, Hflag);
    947 				intprt(b1, 21, qv->qv_usage,
    948 				       HN_NOSPACE | objtypeflags, Hflag);
    949 				if (strcmp(b0, b1)) {
    950 					warnx("Line %u: cannot change usage",
    951 					      lineno);
    952 				}
    953 				continue;
    954 
    955 			} else if (!strncmp(text, "limits:", 7)) {
    956 
    957 				/* limits: soft %llu, hard %llu */
    958 				text += 7;
    959 				text2 = strchr(text, ',');
    960 				if (text2 == NULL) {
    961 					warnx("Line %u: expected comma",
    962 					      lineno);
    963 					goto fail;
    964 				}
    965 				*text2 = '\0';
    966 				text2++;
    967 
    968 				t = skipws(text);
    969 				t = skipword(t);
    970 				t = skipws(t);
    971 				if (intrd(t, &soft, objtypeflags) != 0) {
    972 					warnx("Line %u: Bad number %s",
    973 					      lineno, t);
    974 					goto fail;
    975 				}
    976 				t = skipws(text2);
    977 				t = skipword(t);
    978 				t = skipws(t);
    979 				if (intrd(t, &hard, objtypeflags) != 0) {
    980 					warnx("Line %u: Bad number %s",
    981 					      lineno, t);
    982 					goto fail;
    983 				}
    984 
    985 				/*
    986 				 * Cause time limit to be reset when the quota
    987 				 * is next used if previously had no soft limit
    988 				 * or were under it, but now have a soft limit
    989 				 * and are over it.
    990 				 */
    991 				if (qv->qv_usage && qv->qv_usage >= soft &&
    992 				    (qv->qv_softlimit == 0 ||
    993 				     qv->qv_usage < qv->qv_softlimit)) {
    994 					qv->qv_expiretime = 0;
    995 				}
    996 				if (soft != qv->qv_softlimit ||
    997 				    hard != qv->qv_hardlimit) {
    998 					qv->qv_softlimit = soft;
    999 					qv->qv_hardlimit = hard;
   1000 					qup->source[objtype] = SRC_EDITED;
   1001 				}
   1002 				qup->found = 1;
   1003 
   1004 			} else if (!strncmp(text, "grace:", 6)) {
   1005 
   1006 				text += 6;
   1007 				/* grace: %llu */
   1008 				t = skipws(text);
   1009 				if (timeprd(t, &grace) != 0) {
   1010 					warnx("Line %u: Bad number %s",
   1011 					      lineno, t);
   1012 					goto fail;
   1013 				}
   1014 				if (qup->isdefault || qup->xgrace) {
   1015 					if (grace != qv->qv_grace) {
   1016 						qv->qv_grace = grace;
   1017 						qup->source[objtype] =
   1018 							SRC_EDITED;
   1019 					}
   1020 					qup->found = 1;
   1021 				} else {
   1022 					warnx("Line %u: Cannot set individual "
   1023 					      "grace time on this filesystem",
   1024 					      lineno);
   1025 					goto fail;
   1026 				}
   1027 
   1028 			} else {
   1029 				warnx("Line %u: Unknown/unexpected value line",
   1030 				      lineno);
   1031 				goto fail;
   1032 			}
   1033 		} else if (line[0] == '\t') {
   1034 			text = line + 1;
   1035 			if (*text == '#') {
   1036 				/* commented out; ignore */
   1037 				continue;
   1038 			}
   1039 			else if (!strncmp(text, "blocks:", 7)) {
   1040 				objtype = QUOTA_OBJTYPE_BLOCKS;
   1041 				objtypeflags = HN_B;
   1042 				haveobjtype = true;
   1043 			} else if (!strncmp(text, "inodes:", 7)) {
   1044 				objtype = QUOTA_OBJTYPE_FILES;
   1045 				objtypeflags = 0;
   1046 				haveobjtype = true;
   1047 			} else {
   1048 				warnx("Line %u: Unknown/unexpected object "
   1049 				      "type (%s)", lineno, text);
   1050 				goto fail;
   1051 			}
   1052 		} else {
   1053 			t = strchr(line, ' ');
   1054 			if (t == NULL) {
   1055 				t = strchr(line, ':');
   1056 				if (t == NULL) {
   1057 					t = line + len;
   1058 				}
   1059 			}
   1060 			*t = '\0';
   1061 
   1062 			if (*line == '#') {
   1063 				/* commented out; ignore */
   1064 				continue;
   1065 			}
   1066 
   1067 			for (qup = qlist->head; qup; qup = qup->next) {
   1068 				if (!strcmp(line, qup->fsname))
   1069 					break;
   1070 			}
   1071 			if (qup == NULL) {
   1072 				warnx("Line %u: Filesystem %s invalid or has "
   1073 				      "no quota support", lineno, line);
   1074 				goto fail;
   1075 			}
   1076 			haveobjtype = false;
   1077 		}
   1078 	}
   1079 
   1080 	fclose(fd);
   1081 
   1082 	/*
   1083 	 * Disable quotas for any filesystems that we didn't see,
   1084 	 * because they must have been deleted in the editor.
   1085 	 *
   1086 	 * XXX this should be improved so it results in
   1087 	 * quota_delete(), not just writing out a blank quotaval.
   1088 	 */
   1089 	for (qup = qlist->head; qup; qup = qup->next) {
   1090 		if (qup->found) {
   1091 			qup->found = 0;
   1092 			continue;
   1093 		}
   1094 
   1095 		if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) {
   1096 			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_BLOCKS]);
   1097 			qup->source[QUOTA_OBJTYPE_BLOCKS] = SRC_EDITED;
   1098 		}
   1099 
   1100 		if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) {
   1101 			quotaval_clear(&qup->qv[QUOTA_OBJTYPE_FILES]);
   1102 			qup->source[QUOTA_OBJTYPE_FILES] = SRC_EDITED;
   1103 		}
   1104 	}
   1105 	return 0;
   1106 
   1107 fail:
   1108 	sleep(3);
   1109 	fclose(fd);
   1110 	return -1;
   1111 }
   1112 
   1113 ////////////////////////////////////////////////////////////
   1114 // actions
   1115 
   1116 static void
   1117 replicate(const char *fs, int idtype, const char *protoname,
   1118 	  char **names, int numnames)
   1119 {
   1120 	long protoid, id;
   1121 	struct quotalist *protoprivs;
   1122 	struct quotause *qup;
   1123 	int i;
   1124 
   1125 	if ((protoid = getidbyname(protoname, idtype)) == -1)
   1126 		exit(1);
   1127 	protoprivs = getprivs(protoid, 0, idtype, fs);
   1128 	for (qup = protoprivs->head; qup; qup = qup->next) {
   1129 		qup->qv[QO_BLK].qv_expiretime = 0;
   1130 		qup->qv[QO_FL].qv_expiretime = 0;
   1131 		qup->source[QO_BLK] = SRC_EDITED;
   1132 		qup->source[QO_FL] = SRC_EDITED;
   1133 	}
   1134 	for (i=0; i<numnames; i++) {
   1135 		id = getidbyname(names[i], idtype);
   1136 		if (id == -1)
   1137 			continue;
   1138 		putprivs(id, idtype, protoprivs);
   1139 	}
   1140 	/* XXX */
   1141 	/* quotalist_destroy(protoprivs); */
   1142 }
   1143 
   1144 static void
   1145 assign(const char *fs, int idtype,
   1146        char *soft, char *hard, char *grace,
   1147        char **names, int numnames)
   1148 {
   1149 	struct quotalist *curprivs;
   1150 	struct quotause *lqup;
   1151 	u_int64_t softb, hardb, softi, hardi;
   1152 	time_t  graceb, gracei;
   1153 	char *str;
   1154 	long id;
   1155 	int dflag;
   1156 	int i;
   1157 
   1158 	if (soft) {
   1159 		str = strsep(&soft, "/");
   1160 		if (str[0] == '\0' || soft == NULL || soft[0] == '\0')
   1161 			usage();
   1162 
   1163 		if (intrd(str, &softb, HN_B) != 0)
   1164 			errx(1, "%s: bad number", str);
   1165 		if (intrd(soft, &softi, 0) != 0)
   1166 			errx(1, "%s: bad number", soft);
   1167 	}
   1168 	if (hard) {
   1169 		str = strsep(&hard, "/");
   1170 		if (str[0] == '\0' || hard == NULL || hard[0] == '\0')
   1171 			usage();
   1172 
   1173 		if (intrd(str, &hardb, HN_B) != 0)
   1174 			errx(1, "%s: bad number", str);
   1175 		if (intrd(hard, &hardi, 0) != 0)
   1176 			errx(1, "%s: bad number", hard);
   1177 	}
   1178 	if (grace) {
   1179 		str = strsep(&grace, "/");
   1180 		if (str[0] == '\0' || grace == NULL || grace[0] == '\0')
   1181 			usage();
   1182 
   1183 		if (timeprd(str, &graceb) != 0)
   1184 			errx(1, "%s: bad number", str);
   1185 		if (timeprd(grace, &gracei) != 0)
   1186 			errx(1, "%s: bad number", grace);
   1187 	}
   1188 	for (i=0; i<numnames; i++) {
   1189 		if (names[i] == NULL) {
   1190 			id = 0;
   1191 			dflag = 1;
   1192 		} else {
   1193 			id = getidbyname(names[i], idtype);
   1194 			if (id == -1)
   1195 				continue;
   1196 			dflag = 0;
   1197 		}
   1198 
   1199 		curprivs = getprivs(id, dflag, idtype, fs);
   1200 		for (lqup = curprivs->head; lqup; lqup = lqup->next) {
   1201 			struct quotaval *q = lqup->qv;
   1202 			if (soft) {
   1203 				if (!dflag && softb &&
   1204 				    q[QO_BLK].qv_usage >= softb &&
   1205 				    (q[QO_BLK].qv_softlimit == 0 ||
   1206 				     q[QO_BLK].qv_usage <
   1207 				     q[QO_BLK].qv_softlimit))
   1208 					q[QO_BLK].qv_expiretime = 0;
   1209 				if (!dflag && softi &&
   1210 				    q[QO_FL].qv_usage >= softb &&
   1211 				    (q[QO_FL].qv_softlimit == 0 ||
   1212 				     q[QO_FL].qv_usage <
   1213 				     q[QO_FL].qv_softlimit))
   1214 					q[QO_FL].qv_expiretime = 0;
   1215 				q[QO_BLK].qv_softlimit = softb;
   1216 				q[QO_FL].qv_softlimit = softi;
   1217 			}
   1218 			if (hard) {
   1219 				q[QO_BLK].qv_hardlimit = hardb;
   1220 				q[QO_FL].qv_hardlimit = hardi;
   1221 			}
   1222 			if (grace) {
   1223 				q[QO_BLK].qv_grace = graceb;
   1224 				q[QO_FL].qv_grace = gracei;
   1225 			}
   1226 			lqup->source[QO_BLK] = SRC_EDITED;
   1227 			lqup->source[QO_FL] = SRC_EDITED;
   1228 		}
   1229 		putprivs(id, idtype, curprivs);
   1230 		quotalist_destroy(curprivs);
   1231 	}
   1232 }
   1233 
   1234 static void
   1235 clear(const char *fs, int idtype, char **names, int numnames)
   1236 {
   1237 	clearpriv(numnames, names, fs, idtype);
   1238 }
   1239 
   1240 static void
   1241 editone(const char *fs, int idtype, const char *name,
   1242 	int tmpfd, const char *tmppath)
   1243 {
   1244 	struct quotalist *curprivs;
   1245 	long id;
   1246 	int dflag;
   1247 
   1248 	if (name == NULL) {
   1249 		id = 0;
   1250 		dflag = 1;
   1251 	} else {
   1252 		id = getidbyname(name, idtype);
   1253 		if (id == -1)
   1254 			return;
   1255 		dflag = 0;
   1256 	}
   1257 	curprivs = getprivs(id, dflag, idtype, fs);
   1258 
   1259 	if (writeprivs(curprivs, tmpfd, name, idtype,
   1260 		       curprivs->idtypename) == 0)
   1261 		goto fail;
   1262 
   1263 	if (editit(tmppath) == 0)
   1264 		goto fail;
   1265 
   1266 	if (readprivs(curprivs, tmpfd, dflag) < 0)
   1267 		goto fail;
   1268 
   1269 	putprivs(id, idtype, curprivs);
   1270 fail:
   1271 	quotalist_destroy(curprivs);
   1272 }
   1273 
   1274 static void
   1275 edit(const char *fs, int idtype, char **names, int numnames)
   1276 {
   1277 	char tmppath[] = _PATH_TMPFILE;
   1278 	int tmpfd, i;
   1279 
   1280 	tmpfd = mkstemp(tmppath);
   1281 	fchown(tmpfd, getuid(), getgid());
   1282 
   1283 	for (i=0; i<numnames; i++) {
   1284 		editone(fs, idtype, names[i], tmpfd, tmppath);
   1285 	}
   1286 
   1287 	close(tmpfd);
   1288 	unlink(tmppath);
   1289 }
   1290 
   1291 ////////////////////////////////////////////////////////////
   1292 // main
   1293 
   1294 static void
   1295 usage(void)
   1296 {
   1297 	const char *p = getprogname();
   1298 	fprintf(stderr,
   1299 	    "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] "
   1300 		"-d | <username> ...\n"
   1301 	    "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] "
   1302 		"-d | <groupname> ...\n"
   1303 	    "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
   1304 		"-d | <username> ...\n"
   1305 	    "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] "
   1306 		"-d | <groupname> ...\n"
   1307 	    "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n"
   1308 	    "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n",
   1309 	    p, p, p, p, p, p);
   1310 	exit(1);
   1311 }
   1312 
   1313 int
   1314 main(int argc, char *argv[])
   1315 {
   1316 	int idtype;
   1317 	char *protoname;
   1318 	char *soft = NULL, *hard = NULL, *grace = NULL;
   1319 	char *fs = NULL;
   1320 	int ch;
   1321 	int pflag = 0;
   1322 	int cflag = 0;
   1323 	int dflag = 0;
   1324 
   1325 	if (argc < 2)
   1326 		usage();
   1327 	if (getuid())
   1328 		errx(1, "permission denied");
   1329 	protoname = NULL;
   1330 	idtype = QUOTA_IDTYPE_USER;
   1331 	while ((ch = getopt(argc, argv, "Hcdugp:s:h:t:f:")) != -1) {
   1332 		switch(ch) {
   1333 		case 'H':
   1334 			Hflag++;
   1335 			break;
   1336 		case 'c':
   1337 			cflag++;
   1338 			break;
   1339 		case 'd':
   1340 			dflag++;
   1341 			break;
   1342 		case 'p':
   1343 			protoname = optarg;
   1344 			pflag++;
   1345 			break;
   1346 		case 'g':
   1347 			idtype = QUOTA_IDTYPE_GROUP;
   1348 			break;
   1349 		case 'u':
   1350 			idtype = QUOTA_IDTYPE_USER;
   1351 			break;
   1352 		case 's':
   1353 			soft = optarg;
   1354 			break;
   1355 		case 'h':
   1356 			hard = optarg;
   1357 			break;
   1358 		case 't':
   1359 			grace = optarg;
   1360 			break;
   1361 		case 'f':
   1362 			fs = optarg;
   1363 			break;
   1364 		default:
   1365 			usage();
   1366 		}
   1367 	}
   1368 	argc -= optind;
   1369 	argv += optind;
   1370 
   1371 	if (pflag) {
   1372 		if (soft || hard || grace || dflag || cflag)
   1373 			usage();
   1374 		replicate(fs, idtype, protoname, argv, argc);
   1375 	} else if (soft || hard || grace) {
   1376 		if (cflag)
   1377 			usage();
   1378 		if (dflag) {
   1379 			/* use argv[argc], which is null, to mean 'default' */
   1380 			argc++;
   1381 		}
   1382 		assign(fs, idtype, soft, hard, grace, argv, argc);
   1383 	} else if (cflag) {
   1384 		if (dflag)
   1385 			usage();
   1386 		clear(fs, idtype, argv, argc);
   1387 	} else {
   1388 		if (dflag) {
   1389 			/* use argv[argc], which is null, to mean 'default' */
   1390 			argc++;
   1391 		}
   1392 		edit(fs, idtype, argv, argc);
   1393 	}
   1394 	return 0;
   1395 }
   1396