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