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