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