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