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