Home | History | Annotate | Line # | Download | only in edquota
edquota.c revision 1.24
      1 /*
      2  * Copyright (c) 1980, 1990, 1993
      3  *	The Regents of the University of California.  All rights reserved.
      4  *
      5  * This code is derived from software contributed to Berkeley by
      6  * Robert Elz at The University of Melbourne.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  * 1. Redistributions of source code must retain the above copyright
     12  *    notice, this list of conditions and the following disclaimer.
     13  * 2. Redistributions in binary form must reproduce the above copyright
     14  *    notice, this list of conditions and the following disclaimer in the
     15  *    documentation and/or other materials provided with the distribution.
     16  * 3. Neither the name of the University nor the names of its contributors
     17  *    may be used to endorse or promote products derived from this software
     18  *    without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     30  * SUCH DAMAGE.
     31  */
     32 
     33 #include <sys/cdefs.h>
     34 #ifndef lint
     35 __COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\n\
     36 	The Regents of the University of California.  All rights reserved.\n");
     37 #endif /* not lint */
     38 
     39 #ifndef lint
     40 #if 0
     41 static char sccsid[] = "from: @(#)edquota.c	8.3 (Berkeley) 4/27/95";
     42 #else
     43 __RCSID("$NetBSD: edquota.c,v 1.24 2003/08/07 11:25:20 agc Exp $");
     44 #endif
     45 #endif /* not lint */
     46 
     47 /*
     48  * Disk quota editor.
     49  */
     50 #include <sys/param.h>
     51 #include <sys/stat.h>
     52 #include <sys/file.h>
     53 #include <sys/wait.h>
     54 #include <sys/queue.h>
     55 #include <ufs/ufs/quota.h>
     56 #include <err.h>
     57 #include <errno.h>
     58 #include <fstab.h>
     59 #include <pwd.h>
     60 #include <grp.h>
     61 #include <ctype.h>
     62 #include <signal.h>
     63 #include <stdio.h>
     64 #include <stdlib.h>
     65 #include <string.h>
     66 #include <unistd.h>
     67 #include "pathnames.h"
     68 
     69 char *qfname = QUOTAFILENAME;
     70 char *qfextension[] = INITQFNAMES;
     71 char *quotagroup = QUOTAGROUP;
     72 char tmpfil[] = _PATH_TMP;
     73 
     74 struct quotause {
     75 	struct	quotause *next;
     76 	long	flags;
     77 	struct	dqblk dqblk;
     78 	char	fsname[MAXPATHLEN + 1];
     79 	char	qfname[1];	/* actually longer */
     80 };
     81 #define	FOUND	0x01
     82 
     83 #define MAX_TMPSTR	(100+MAXPATHLEN)
     84 
     85 int	main __P((int, char **));
     86 void	usage __P((void));
     87 int	getentry __P((char *, int));
     88 struct quotause *
     89 	getprivs __P((long, int, char *));
     90 void	putprivs __P((long, int, struct quotause *));
     91 int	editit __P((char *));
     92 int	writeprivs __P((struct quotause *, int, char *, int));
     93 int	readprivs __P((struct quotause *, int));
     94 int	writetimes __P((struct quotause *, int, int));
     95 int	readtimes __P((struct quotause *, int));
     96 char *	cvtstoa __P((time_t));
     97 int	cvtatos __P((time_t, char *, time_t *));
     98 void	freeprivs __P((struct quotause *));
     99 int	alldigits __P((char *));
    100 int	hasquota __P((struct fstab *, int, char **));
    101 
    102 int
    103 main(argc, argv)
    104 	int argc;
    105 	char **argv;
    106 {
    107 	struct quotause *qup, *protoprivs, *curprivs;
    108 	long id, protoid;
    109 	int quotatype, tmpfd;
    110 	char *protoname;
    111 	char *soft = NULL, *hard = NULL;
    112 	char *fs = NULL;
    113 	int ch;
    114 	int tflag = 0, pflag = 0;
    115 
    116 	if (argc < 2)
    117 		usage();
    118 	if (getuid())
    119 		errx(1, "permission denied");
    120 	protoname = NULL;
    121 	quotatype = USRQUOTA;
    122 	while ((ch = getopt(argc, argv, "ugtp:s:h:f:")) != -1) {
    123 		switch(ch) {
    124 		case 'p':
    125 			protoname = optarg;
    126 			pflag++;
    127 			break;
    128 		case 'g':
    129 			quotatype = GRPQUOTA;
    130 			break;
    131 		case 'u':
    132 			quotatype = USRQUOTA;
    133 			break;
    134 		case 't':
    135 			tflag++;
    136 			break;
    137 		case 's':
    138 			soft = optarg;
    139 			break;
    140 		case 'h':
    141 			hard = optarg;
    142 			break;
    143 		case 'f':
    144 			fs = optarg;
    145 			break;
    146 		default:
    147 			usage();
    148 		}
    149 	}
    150 	argc -= optind;
    151 	argv += optind;
    152 	if (pflag) {
    153 		if (soft || hard)
    154 			usage();
    155 		if ((protoid = getentry(protoname, quotatype)) == -1)
    156 			exit(1);
    157 		protoprivs = getprivs(protoid, quotatype, fs);
    158 		for (qup = protoprivs; qup; qup = qup->next) {
    159 			qup->dqblk.dqb_btime = 0;
    160 			qup->dqblk.dqb_itime = 0;
    161 		}
    162 		while (argc-- > 0) {
    163 			if ((id = getentry(*argv++, quotatype)) < 0)
    164 				continue;
    165 			putprivs(id, quotatype, protoprivs);
    166 		}
    167 		exit(0);
    168 	}
    169 	if (soft || hard) {
    170 		struct quotause *qup;
    171 		u_int32_t softb, hardb, softi, hardi;
    172 		if (tflag)
    173 			usage();
    174 		if (soft) {
    175 			if (sscanf(soft, "%d/%d", &softb, &softi) != 2)
    176 				usage();
    177 			softb = btodb((u_quad_t)softb * 1024);
    178 		}
    179 		if (hard) {
    180 			if (sscanf(hard, "%d/%d", &hardb, &hardi) != 2)
    181 				usage();
    182 			hardb = btodb((u_quad_t)hardb * 1024);
    183 		}
    184 		for ( ; argc > 0; argc--, argv++) {
    185 			if ((id = getentry(*argv, quotatype)) == -1)
    186 				continue;
    187 			curprivs = getprivs(id, quotatype, fs);
    188 			for (qup = curprivs; qup; qup = qup->next) {
    189 				if (soft) {
    190 					if (softb &&
    191 					    qup->dqblk.dqb_curblocks >= softb &&
    192 					    (qup->dqblk.dqb_bsoftlimit == 0 ||
    193 					    qup->dqblk.dqb_curblocks <
    194 					    qup->dqblk.dqb_bsoftlimit))
    195 						qup->dqblk.dqb_btime = 0;
    196 					if (softi &&
    197 					    qup->dqblk.dqb_curinodes >= softi &&
    198 					    (qup->dqblk.dqb_isoftlimit == 0 ||
    199 					    qup->dqblk.dqb_curinodes <
    200 					    qup->dqblk.dqb_isoftlimit))
    201 						qup->dqblk.dqb_itime = 0;
    202 					qup->dqblk.dqb_bsoftlimit = softb;
    203 					qup->dqblk.dqb_isoftlimit = softi;
    204 				}
    205 				if (hard) {
    206 					qup->dqblk.dqb_bhardlimit = hardb;
    207 					qup->dqblk.dqb_ihardlimit = hardi;
    208 				}
    209 			}
    210 			putprivs(id, quotatype, curprivs);
    211 			freeprivs(curprivs);
    212 		}
    213 		exit(0);
    214 	}
    215 	tmpfd = mkstemp(tmpfil);
    216 	fchown(tmpfd, getuid(), getgid());
    217 	if (tflag) {
    218 		if (soft || hard)
    219 			usage();
    220 		protoprivs = getprivs(0, quotatype, fs);
    221 		if (writetimes(protoprivs, tmpfd, quotatype) == 0)
    222 			exit(1);
    223 		if (editit(tmpfil) && readtimes(protoprivs, tmpfd))
    224 			putprivs(0, quotatype, protoprivs);
    225 		freeprivs(protoprivs);
    226 		exit(0);
    227 	}
    228 	for ( ; argc > 0; argc--, argv++) {
    229 		if ((id = getentry(*argv, quotatype)) == -1)
    230 			continue;
    231 		curprivs = getprivs(id, quotatype, fs);
    232 		if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
    233 			continue;
    234 		if (editit(tmpfil) && readprivs(curprivs, tmpfd))
    235 			putprivs(id, quotatype, curprivs);
    236 		freeprivs(curprivs);
    237 	}
    238 	close(tmpfd);
    239 	unlink(tmpfil);
    240 	exit(0);
    241 }
    242 
    243 void
    244 usage()
    245 {
    246 	fprintf(stderr,
    247 	    "Usage: edquota [-u] [-p username] [-f filesystem] username ...\n"
    248 	    "\tedquota -g [-p groupname] [-f filesystem] groupname ...\n"
    249 	    "\tedquota [-u] [-f filesystem] [-s b#/i#] [-h b#/i#] username ...\n"
    250 	    "\tedquota -g [-f filesystem] [-s b#/i#] [-h b#/i#] groupname ...\n"
    251 	    "\tedquota [-u] [-f filesystem] -t\n"
    252 	    "\tedquota -g [-f filesystem] -t\n"
    253 	    );
    254 	exit(1);
    255 }
    256 
    257 /*
    258  * This routine converts a name for a particular quota type to
    259  * an identifier. This routine must agree with the kernel routine
    260  * getinoquota as to the interpretation of quota types.
    261  */
    262 int
    263 getentry(name, quotatype)
    264 	char *name;
    265 	int quotatype;
    266 {
    267 	struct passwd *pw;
    268 	struct group *gr;
    269 
    270 	if (alldigits(name))
    271 		return (atoi(name));
    272 	switch(quotatype) {
    273 	case USRQUOTA:
    274 		if ((pw = getpwnam(name)) != NULL)
    275 			return (pw->pw_uid);
    276 		warnx("%s: no such user", name);
    277 		break;
    278 	case GRPQUOTA:
    279 		if ((gr = getgrnam(name)) != NULL)
    280 			return (gr->gr_gid);
    281 		warnx("%s: no such group", name);
    282 		break;
    283 	default:
    284 		warnx("%d: unknown quota type", quotatype);
    285 		break;
    286 	}
    287 	sleep(1);
    288 	return (-1);
    289 }
    290 
    291 /*
    292  * Collect the requested quota information.
    293  */
    294 struct quotause *
    295 getprivs(id, quotatype, filesys)
    296 	long id;
    297 	int quotatype;
    298 	char *filesys;
    299 {
    300 	struct fstab *fs;
    301 	struct quotause *qup, *quptail;
    302 	struct quotause *quphead;
    303 	int qcmd, qupsize, fd;
    304 	char *qfpathname;
    305 	static int warned = 0;
    306 
    307 	setfsent();
    308 	quptail = NULL;
    309 	quphead = (struct quotause *)0;
    310 	qcmd = QCMD(Q_GETQUOTA, quotatype);
    311 	while ((fs = getfsent()) != NULL) {
    312 		if (strcmp(fs->fs_vfstype, "ffs"))
    313 			continue;
    314 		if (filesys && strcmp(fs->fs_spec, filesys) != 0 &&
    315 		    strcmp(fs->fs_file, filesys) != 0)
    316 			continue;
    317 		if (!hasquota(fs, quotatype, &qfpathname))
    318 			continue;
    319 		qupsize = sizeof(*qup) + strlen(qfpathname);
    320 		if ((qup = (struct quotause *)malloc(qupsize)) == NULL)
    321 			errx(2, "out of memory");
    322 		if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
    323 	    		if (errno == EOPNOTSUPP && !warned) {
    324 				warned++;
    325 				warnx(
    326 				    "Quotas are not compiled into this kernel");
    327 				sleep(3);
    328 			}
    329 			if ((fd = open(qfpathname, O_RDONLY)) < 0) {
    330 				fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
    331 				if (fd < 0 && errno != ENOENT) {
    332 					warnx("open `%s'", qfpathname);
    333 					free(qup);
    334 					continue;
    335 				}
    336 				warnx("Creating quota file %s", qfpathname);
    337 				sleep(3);
    338 				(void) fchown(fd, getuid(),
    339 				    getentry(quotagroup, GRPQUOTA));
    340 				(void) fchmod(fd, 0640);
    341 			}
    342 			(void)lseek(fd, (off_t)(id * sizeof(struct dqblk)),
    343 			    SEEK_SET);
    344 			switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
    345 			case 0:			/* EOF */
    346 				/*
    347 				 * Convert implicit 0 quota (EOF)
    348 				 * into an explicit one (zero'ed dqblk)
    349 				 */
    350 				memset((caddr_t)&qup->dqblk, 0,
    351 				    sizeof(struct dqblk));
    352 				break;
    353 
    354 			case sizeof(struct dqblk):	/* OK */
    355 				break;
    356 
    357 			default:		/* ERROR */
    358 				warn("read error in `%s'", qfpathname);
    359 				close(fd);
    360 				free(qup);
    361 				continue;
    362 			}
    363 			close(fd);
    364 		}
    365 		strcpy(qup->qfname, qfpathname);
    366 		strcpy(qup->fsname, fs->fs_file);
    367 		if (quphead == NULL)
    368 			quphead = qup;
    369 		else
    370 			quptail->next = qup;
    371 		quptail = qup;
    372 		qup->next = 0;
    373 	}
    374 	endfsent();
    375 	return (quphead);
    376 }
    377 
    378 /*
    379  * Store the requested quota information.
    380  */
    381 void
    382 putprivs(id, quotatype, quplist)
    383 	long id;
    384 	int quotatype;
    385 	struct quotause *quplist;
    386 {
    387 	struct quotause *qup;
    388 	int qcmd, fd;
    389 
    390 	qcmd = QCMD(Q_SETQUOTA, quotatype);
    391 	for (qup = quplist; qup; qup = qup->next) {
    392 		if (quotactl(qup->fsname, qcmd, id, &qup->dqblk) == 0)
    393 			continue;
    394 		if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
    395 			warnx("open `%s'", qup->qfname);
    396 		} else {
    397 			(void)lseek(fd,
    398 			    (off_t)(id * (long)sizeof (struct dqblk)),
    399 			    SEEK_SET);
    400 			if (write(fd, &qup->dqblk, sizeof (struct dqblk)) !=
    401 			    sizeof (struct dqblk))
    402 				warnx("writing `%s'", qup->qfname);
    403 			close(fd);
    404 		}
    405 	}
    406 }
    407 
    408 /*
    409  * Take a list of privileges and get it edited.
    410  */
    411 int
    412 editit(tmpfile)
    413 	char *tmpfile;
    414 {
    415 	long omask;
    416 	int pid, stat;
    417 	char p[MAX_TMPSTR];
    418 
    419 	omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
    420  top:
    421 	if ((pid = fork()) < 0) {
    422 
    423 		if (errno == EPROCLIM) {
    424 			warnx("You have too many processes");
    425 			return(0);
    426 		}
    427 		if (errno == EAGAIN) {
    428 			sleep(1);
    429 			goto top;
    430 		}
    431 		warn("fork");
    432 		return (0);
    433 	}
    434 	if (pid == 0) {
    435 		char *ed;
    436 
    437 		sigsetmask(omask);
    438 		setgid(getgid());
    439 		setuid(getuid());
    440 		if ((ed = getenv("EDITOR")) == (char *)0)
    441 			ed = _PATH_VI;
    442 		if (strlen(ed) + strlen(tmpfile) + 2 >= MAX_TMPSTR) {
    443 			err (1, "%s", "editor or filename too long");
    444 		}
    445 		snprintf (p, MAX_TMPSTR, "%s %s", ed, tmpfile);
    446 		execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL);
    447 		err(1, "%s", ed);
    448 	}
    449 	waitpid(pid, &stat, 0);
    450 	sigsetmask(omask);
    451 	if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0)
    452 		return (0);
    453 	return (1);
    454 }
    455 
    456 /*
    457  * Convert a quotause list to an ASCII file.
    458  */
    459 int
    460 writeprivs(quplist, outfd, name, quotatype)
    461 	struct quotause *quplist;
    462 	int outfd;
    463 	char *name;
    464 	int quotatype;
    465 {
    466 	struct quotause *qup;
    467 	FILE *fd;
    468 
    469 	ftruncate(outfd, 0);
    470 	(void)lseek(outfd, (off_t)0, SEEK_SET);
    471 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
    472 		errx(1, "fdopen `%s'", tmpfil);
    473 	fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
    474 	for (qup = quplist; qup; qup = qup->next) {
    475 		fprintf(fd, "%s: %s %d, limits (soft = %d, hard = %d)\n",
    476 		    qup->fsname, "blocks in use:",
    477 		    (int)(dbtob((u_quad_t)qup->dqblk.dqb_curblocks) / 1024),
    478 		    (int)(dbtob((u_quad_t)qup->dqblk.dqb_bsoftlimit) / 1024),
    479 		    (int)(dbtob((u_quad_t)qup->dqblk.dqb_bhardlimit) / 1024));
    480 		fprintf(fd, "%s %d, limits (soft = %d, hard = %d)\n",
    481 		    "\tinodes in use:", qup->dqblk.dqb_curinodes,
    482 		    qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit);
    483 	}
    484 	fclose(fd);
    485 	return (1);
    486 }
    487 
    488 /*
    489  * Merge changes to an ASCII file into a quotause list.
    490  */
    491 int
    492 readprivs(quplist, infd)
    493 	struct quotause *quplist;
    494 	int infd;
    495 {
    496 	struct quotause *qup;
    497 	FILE *fd;
    498 	int cnt;
    499 	char *cp;
    500 	struct dqblk dqblk;
    501 	char *fsp, line1[BUFSIZ], line2[BUFSIZ];
    502 
    503 	(void)lseek(infd, (off_t)0, SEEK_SET);
    504 	fd = fdopen(dup(infd), "r");
    505 	if (fd == NULL) {
    506 		warn("Can't re-read temp file");
    507 		return (0);
    508 	}
    509 	/*
    510 	 * Discard title line, then read pairs of lines to process.
    511 	 */
    512 	(void) fgets(line1, sizeof (line1), fd);
    513 	while (fgets(line1, sizeof (line1), fd) != NULL &&
    514 	       fgets(line2, sizeof (line2), fd) != NULL) {
    515 		if ((fsp = strtok(line1, " \t:")) == NULL) {
    516 			warnx("%s: bad format", line1);
    517 			return (0);
    518 		}
    519 		if ((cp = strtok((char *)0, "\n")) == NULL) {
    520 			warnx("%s: %s: bad format", fsp,
    521 			    &fsp[strlen(fsp) + 1]);
    522 			return (0);
    523 		}
    524 		cnt = sscanf(cp,
    525 		    " blocks in use: %d, limits (soft = %d, hard = %d)",
    526 		    &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit,
    527 		    &dqblk.dqb_bhardlimit);
    528 		if (cnt != 3) {
    529 			warnx("%s:%s: bad format", fsp, cp);
    530 			return (0);
    531 		}
    532 		dqblk.dqb_curblocks = btodb((u_quad_t)
    533 		    dqblk.dqb_curblocks * 1024);
    534 		dqblk.dqb_bsoftlimit = btodb((u_quad_t)
    535 		    dqblk.dqb_bsoftlimit * 1024);
    536 		dqblk.dqb_bhardlimit = btodb((u_quad_t)
    537 		    dqblk.dqb_bhardlimit * 1024);
    538 		if ((cp = strtok(line2, "\n")) == NULL) {
    539 			warnx("%s: %s: bad format", fsp, line2);
    540 			return (0);
    541 		}
    542 		cnt = sscanf(cp,
    543 		    "\tinodes in use: %d, limits (soft = %d, hard = %d)",
    544 		    &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit,
    545 		    &dqblk.dqb_ihardlimit);
    546 		if (cnt != 3) {
    547 			warnx("%s: %s: bad format", fsp, line2);
    548 			return (0);
    549 		}
    550 		for (qup = quplist; qup; qup = qup->next) {
    551 			if (strcmp(fsp, qup->fsname))
    552 				continue;
    553 			/*
    554 			 * Cause time limit to be reset when the quota
    555 			 * is next used if previously had no soft limit
    556 			 * or were under it, but now have a soft limit
    557 			 * and are over it.
    558 			 */
    559 			if (dqblk.dqb_bsoftlimit &&
    560 			    qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
    561 			    (qup->dqblk.dqb_bsoftlimit == 0 ||
    562 			     qup->dqblk.dqb_curblocks <
    563 			     qup->dqblk.dqb_bsoftlimit))
    564 				qup->dqblk.dqb_btime = 0;
    565 			if (dqblk.dqb_isoftlimit &&
    566 			    qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
    567 			    (qup->dqblk.dqb_isoftlimit == 0 ||
    568 			     qup->dqblk.dqb_curinodes <
    569 			     qup->dqblk.dqb_isoftlimit))
    570 				qup->dqblk.dqb_itime = 0;
    571 			qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
    572 			qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
    573 			qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
    574 			qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
    575 			qup->flags |= FOUND;
    576 			if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
    577 			    dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes)
    578 				break;
    579 			warnx("%s: cannot change current allocation", fsp);
    580 			break;
    581 		}
    582 	}
    583 	fclose(fd);
    584 	/*
    585 	 * Disable quotas for any filesystems that have not been found.
    586 	 */
    587 	for (qup = quplist; qup; qup = qup->next) {
    588 		if (qup->flags & FOUND) {
    589 			qup->flags &= ~FOUND;
    590 			continue;
    591 		}
    592 		qup->dqblk.dqb_bsoftlimit = 0;
    593 		qup->dqblk.dqb_bhardlimit = 0;
    594 		qup->dqblk.dqb_isoftlimit = 0;
    595 		qup->dqblk.dqb_ihardlimit = 0;
    596 	}
    597 	return (1);
    598 }
    599 
    600 /*
    601  * Convert a quotause list to an ASCII file of grace times.
    602  */
    603 int
    604 writetimes(quplist, outfd, quotatype)
    605 	struct quotause *quplist;
    606 	int outfd;
    607 	int quotatype;
    608 {
    609 	struct quotause *qup;
    610 	FILE *fd;
    611 
    612 	ftruncate(outfd, 0);
    613 	(void)lseek(outfd, (off_t)0, SEEK_SET);
    614 	if ((fd = fdopen(dup(outfd), "w")) == NULL)
    615 		err(1, "fdopen `%s'", tmpfil);
    616 	fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
    617 	fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
    618 	    qfextension[quotatype]);
    619 	for (qup = quplist; qup; qup = qup->next) {
    620 		fprintf(fd, "%s: block grace period: %s, ",
    621 		    qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
    622 		fprintf(fd, "file grace period: %s\n",
    623 		    cvtstoa(qup->dqblk.dqb_itime));
    624 	}
    625 	fclose(fd);
    626 	return (1);
    627 }
    628 
    629 /*
    630  * Merge changes of grace times in an ASCII file into a quotause list.
    631  */
    632 int
    633 readtimes(quplist, infd)
    634 	struct quotause *quplist;
    635 	int infd;
    636 {
    637 	struct quotause *qup;
    638 	FILE *fd;
    639 	int cnt;
    640 	char *cp;
    641 	long litime, lbtime;
    642 	time_t itime, btime, iseconds, bseconds;
    643 	char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
    644 
    645 	(void)lseek(infd, (off_t)0, SEEK_SET);
    646 	fd = fdopen(dup(infd), "r");
    647 	if (fd == NULL) {
    648 		warnx("Can't re-read temp file!!");
    649 		return (0);
    650 	}
    651 	/*
    652 	 * Discard two title lines, then read lines to process.
    653 	 */
    654 	(void) fgets(line1, sizeof (line1), fd);
    655 	(void) fgets(line1, sizeof (line1), fd);
    656 	while (fgets(line1, sizeof (line1), fd) != NULL) {
    657 		if ((fsp = strtok(line1, " \t:")) == NULL) {
    658 			warnx("%s: bad format", line1);
    659 			return (0);
    660 		}
    661 		if ((cp = strtok((char *)0, "\n")) == NULL) {
    662 			warnx("%s: %s: bad format", fsp,
    663 			    &fsp[strlen(fsp) + 1]);
    664 			return (0);
    665 		}
    666 		cnt = sscanf(cp,
    667 		    " block grace period: %ld %s file grace period: %ld %s",
    668 		    &lbtime, bunits, &litime, iunits);
    669 		if (cnt != 4) {
    670 			warnx("%s:%s: bad format", fsp, cp);
    671 			return (0);
    672 		}
    673 		itime = (time_t)litime;
    674 		btime = (time_t)lbtime;
    675 		if (cvtatos(btime, bunits, &bseconds) == 0)
    676 			return (0);
    677 		if (cvtatos(itime, iunits, &iseconds) == 0)
    678 			return (0);
    679 		for (qup = quplist; qup; qup = qup->next) {
    680 			if (strcmp(fsp, qup->fsname))
    681 				continue;
    682 			qup->dqblk.dqb_btime = bseconds;
    683 			qup->dqblk.dqb_itime = iseconds;
    684 			qup->flags |= FOUND;
    685 			break;
    686 		}
    687 	}
    688 	fclose(fd);
    689 	/*
    690 	 * reset default grace periods for any filesystems
    691 	 * that have not been found.
    692 	 */
    693 	for (qup = quplist; qup; qup = qup->next) {
    694 		if (qup->flags & FOUND) {
    695 			qup->flags &= ~FOUND;
    696 			continue;
    697 		}
    698 		qup->dqblk.dqb_btime = 0;
    699 		qup->dqblk.dqb_itime = 0;
    700 	}
    701 	return (1);
    702 }
    703 
    704 /*
    705  * Convert seconds to ASCII times.
    706  */
    707 char *
    708 cvtstoa(time)
    709 	time_t time;
    710 {
    711 	static char buf[20];
    712 
    713 	if (time % (24 * 60 * 60) == 0) {
    714 		time /= 24 * 60 * 60;
    715 		snprintf(buf, sizeof buf, "%ld day%s", (long)time,
    716 		    time == 1 ? "" : "s");
    717 	} else if (time % (60 * 60) == 0) {
    718 		time /= 60 * 60;
    719 		sprintf(buf, "%ld hour%s", (long)time, time == 1 ? "" : "s");
    720 	} else if (time % 60 == 0) {
    721 		time /= 60;
    722 		sprintf(buf, "%ld minute%s", (long)time, time == 1 ? "" : "s");
    723 	} else
    724 		sprintf(buf, "%ld second%s", (long)time, time == 1 ? "" : "s");
    725 	return (buf);
    726 }
    727 
    728 /*
    729  * Convert ASCII input times to seconds.
    730  */
    731 int
    732 cvtatos(time, units, seconds)
    733 	time_t time;
    734 	char *units;
    735 	time_t *seconds;
    736 {
    737 
    738 	if (memcmp(units, "second", 6) == 0)
    739 		*seconds = time;
    740 	else if (memcmp(units, "minute", 6) == 0)
    741 		*seconds = time * 60;
    742 	else if (memcmp(units, "hour", 4) == 0)
    743 		*seconds = time * 60 * 60;
    744 	else if (memcmp(units, "day", 3) == 0)
    745 		*seconds = time * 24 * 60 * 60;
    746 	else {
    747 		printf("%s: bad units, specify %s\n", units,
    748 		    "days, hours, minutes, or seconds");
    749 		return (0);
    750 	}
    751 	return (1);
    752 }
    753 
    754 /*
    755  * Free a list of quotause structures.
    756  */
    757 void
    758 freeprivs(quplist)
    759 	struct quotause *quplist;
    760 {
    761 	struct quotause *qup, *nextqup;
    762 
    763 	for (qup = quplist; qup; qup = nextqup) {
    764 		nextqup = qup->next;
    765 		free(qup);
    766 	}
    767 }
    768 
    769 /*
    770  * Check whether a string is completely composed of digits.
    771  */
    772 int
    773 alldigits(s)
    774 	char *s;
    775 {
    776 	int c;
    777 
    778 	c = *s++;
    779 	do {
    780 		if (!isdigit(c))
    781 			return (0);
    782 	} while ((c = *s++) != 0);
    783 	return (1);
    784 }
    785 
    786 /*
    787  * Check to see if a particular quota is to be enabled.
    788  */
    789 int
    790 hasquota(fs, type, qfnamep)
    791 	struct fstab *fs;
    792 	int type;
    793 	char **qfnamep;
    794 {
    795 	char *opt;
    796 	char *cp;
    797 	static char initname, usrname[100], grpname[100];
    798 	static char buf[BUFSIZ];
    799 
    800 	if (!initname) {
    801 		sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
    802 		sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
    803 		initname = 1;
    804 	}
    805 	strcpy(buf, fs->fs_mntops);
    806 	cp = NULL;
    807 	for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
    808 		if ((cp = strchr(opt, '=')) != NULL)
    809 			*cp++ = '\0';
    810 		if (type == USRQUOTA && strcmp(opt, usrname) == 0)
    811 			break;
    812 		if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
    813 			break;
    814 	}
    815 	if (!opt)
    816 		return (0);
    817 	if (cp) {
    818 		*qfnamep = cp;
    819 		return (1);
    820 	}
    821 	(void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
    822 	*qfnamep = buf;
    823 	return (1);
    824 }
    825