Home | History | Annotate | Line # | Download | only in edquota
edquota.c revision 1.52
      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