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