general cleanup, use new err(3) routines
[unix-history] / usr / src / usr.sbin / edquota / edquota.c
index 305d706..57f0d75 100644 (file)
+/*
+ * Copyright (c) 1980, 1990 Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Robert Elz at The University of Melbourne.
+ *
+ * %sccs.include.redist.c%
+ */
+
 #ifndef lint
 #ifndef lint
-static char sccsid[] = "@(#)edquota.c  4.4 (Berkeley, from Melbourne) %G%";
-#endif
+char copyright[] =
+"@(#) Copyright (c) 1980, 1990 Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)edquota.c  5.16 (Berkeley) %G%";
+#endif /* not lint */
 
 /*
  * Disk quota editor.
  */
 
 /*
  * Disk quota editor.
  */
-#include <stdio.h>
-#include <signal.h>
-#include <errno.h>
-#include <pwd.h>
-#include <ctype.h>
-#include <fstab.h>
-
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/file.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #include <sys/file.h>
-#define QUOTA
-#include <sys/quota.h>
-
-#define        DEFEDITOR       "/usr/ucb/vi"
+#include <sys/wait.h>
+#include <ufs/ufs/quota.h>
+#include <errno.h>
+#include <fstab.h>
+#include <pwd.h>
+#include <grp.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include "pathnames.h"
 
 
-struct dquot dq[NMOUNT];
-struct dquot odq[NMOUNT];
-char   dqf[NMOUNT][MAXPATHLEN + 1];
-char   odqf[NMOUNT][MAXPATHLEN + 1];
+char *qfname = QUOTAFILENAME;
+char *qfextension[] = INITQFNAMES;
+char *quotagroup = QUOTAGROUP;
+char tmpfil[] = _PATH_TMP;
 
 
-char   tmpfil[] = "/tmp/EdP.aXXXXX";
-char   *qfname = "quotas";
-char   *getenv();
+struct quotause {
+       struct  quotause *next;
+       long    flags;
+       struct  dqblk dqblk;
+       char    fsname[MAXPATHLEN + 1];
+       char    qfname[1];      /* actually longer */
+} *getprivs();
+#define        FOUND   0x01
 
 main(argc, argv)
 
 main(argc, argv)
-       char **argv;
+       register char **argv;
+       int argc;
 {
 {
-       int uid;
-       char *arg0;
-
-       mktemp(tmpfil);
-       close(creat(tmpfil, 0600));
-       chown(tmpfil, getuid(), getgid());
-       arg0 = *argv++;
-       if (argc < 2) {
-               fprintf(stderr, "Usage: %s [-p username] username ...\n", arg0);
-               unlink(tmpfil);
-               exit(1);
-       }
-       --argc;
+       register struct quotause *qup, *protoprivs, *curprivs;
+       extern char *optarg;
+       extern int optind;
+       register long id, protoid;
+       register int quotatype, tmpfd;
+       char *protoname, ch;
+       int tflag = 0, pflag = 0;
+
+       if (argc < 2)
+               usage();
        if (getuid()) {
        if (getuid()) {
-               fprintf(stderr, "%s: permission denied\n", arg0);
-               unlink(tmpfil);
+               fprintf(stderr, "edquota: permission denied\n");
                exit(1);
        }
                exit(1);
        }
-       if (argc > 2 && strcmp(*argv, "-p") == 0) {
-               argc--, argv++;
-               uid = getentry(*argv++);
-               if (uid < 0) {
-                       unlink(tmpfil);
+       quotatype = USRQUOTA;
+       while ((ch = getopt(argc, argv, "ugtp:")) != EOF) {
+               switch(ch) {
+               case 'p':
+                       protoname = optarg;
+                       pflag++;
+                       break;
+               case 'g':
+                       quotatype = GRPQUOTA;
+                       break;
+               case 'u':
+                       quotatype = USRQUOTA;
+                       break;
+               case 't':
+                       tflag++;
+                       break;
+               default:
+                       usage();
+               }
+       }
+       argc -= optind;
+       argv += optind;
+       if (pflag) {
+               if ((protoid = getentry(protoname, quotatype)) == -1)
                        exit(1);
                        exit(1);
+               protoprivs = getprivs(protoid, quotatype);
+               for (qup = protoprivs; qup; qup = qup->next) {
+                       qup->dqblk.dqb_btime = 0;
+                       qup->dqblk.dqb_itime = 0;
                }
                }
-               getprivs(uid);
-               argc--;
                while (argc-- > 0) {
                while (argc-- > 0) {
-                       uid = getentry(*argv++);
-                       if (uid < 0)
+                       if ((id = getentry(*argv++, quotatype)) < 0)
                                continue;
                                continue;
-                       getdiscq(uid, odq, odqf);
-                       putprivs(uid);
+                       putprivs(id, quotatype, protoprivs);
                }
                }
-               unlink(tmpfil);
                exit(0);
        }
                exit(0);
        }
-       while (--argc >= 0) {
-               uid = getentry(*argv++);
-               if (uid < 0)
+       tmpfd = mkstemp(tmpfil);
+       fchown(tmpfd, getuid(), getgid());
+       if (tflag) {
+               protoprivs = getprivs(0, quotatype);
+               if (writetimes(protoprivs, tmpfd, quotatype) == 0)
+                       exit(1);
+               if (editit(tmpfil) && readtimes(protoprivs, tmpfd))
+                       putprivs(0, quotatype, protoprivs);
+               freeprivs(protoprivs);
+               exit(0);
+       }
+       for ( ; argc > 0; argc--, argv++) {
+               if ((id = getentry(*argv, quotatype)) == -1)
                        continue;
                        continue;
-               getprivs(uid);
-               if (editit())
-                       putprivs(uid);
+               curprivs = getprivs(id, quotatype);
+               if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
+                       continue;
+               if (editit(tmpfil) && readprivs(curprivs, tmpfd))
+                       putprivs(id, quotatype, curprivs);
+               freeprivs(curprivs);
        }
        }
+       close(tmpfd);
        unlink(tmpfil);
        exit(0);
 }
 
        unlink(tmpfil);
        exit(0);
 }
 
-getentry(name)
+usage()
+{
+       fprintf(stderr, "%s%s%s%s",
+               "Usage: edquota [-u] [-p username] username ...\n",
+               "\tedquota -g [-p groupname] groupname ...\n",
+               "\tedquota [-u] -t\n", "\tedquota -g -t\n");
+       exit(1);
+}
+
+/*
+ * This routine converts a name for a particular quota type to
+ * an identifier. This routine must agree with the kernel routine
+ * getinoquota as to the interpretation of quota types.
+ */
+getentry(name, quotatype)
        char *name;
        char *name;
+       int quotatype;
 {
        struct passwd *pw;
 {
        struct passwd *pw;
-       int uid;
+       struct group *gr;
 
        if (alldigits(name))
 
        if (alldigits(name))
-               uid = atoi(name);
-       else if (pw = getpwnam(name))
-               uid = pw->pw_uid;
-       else {
+               return (atoi(name));
+       switch(quotatype) {
+       case USRQUOTA:
+               if (pw = getpwnam(name))
+                       return (pw->pw_uid);
                fprintf(stderr, "%s: no such user\n", name);
                fprintf(stderr, "%s: no such user\n", name);
-               sleep(1);
-               return (-1);
+               break;
+       case GRPQUOTA:
+               if (gr = getgrnam(name))
+                       return (gr->gr_gid);
+               fprintf(stderr, "%s: no such group\n", name);
+               break;
+       default:
+               fprintf(stderr, "%d: unknown quota type\n", quotatype);
+               break;
+       }
+       sleep(1);
+       return (-1);
+}
+
+/*
+ * Collect the requested quota information.
+ */
+struct quotause *
+getprivs(id, quotatype)
+       register long id;
+       int quotatype;
+{
+       register struct fstab *fs;
+       register struct quotause *qup, *quptail;
+       struct quotause *quphead;
+       int qcmd, qupsize, fd;
+       char *qfpathname;
+       static int warned = 0;
+       extern int errno;
+
+       setfsent();
+       quphead = (struct quotause *)0;
+       qcmd = QCMD(Q_GETQUOTA, quotatype);
+       while (fs = getfsent()) {
+               if (strcmp(fs->fs_vfstype, "ufs"))
+                       continue;
+               if (!hasquota(fs, quotatype, &qfpathname))
+                       continue;
+               qupsize = sizeof(*qup) + strlen(qfpathname);
+               if ((qup = (struct quotause *)malloc(qupsize)) == NULL) {
+                       fprintf(stderr, "edquota: out of memory\n");
+                       exit(2);
+               }
+               if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) {
+                       if (errno == EOPNOTSUPP && !warned) {
+                               warned++;
+                               fprintf(stderr, "Warning: %s\n",
+                                   "Quotas are not compiled into this kernel");
+                               sleep(3);
+                       }
+                       if ((fd = open(qfpathname, O_RDONLY)) < 0) {
+                               fd = open(qfpathname, O_RDWR|O_CREAT, 0640);
+                               if (fd < 0 && errno != ENOENT) {
+                                       perror(qfpathname);
+                                       free(qup);
+                                       continue;
+                               }
+                               fprintf(stderr, "Creating quota file %s\n",
+                                   qfpathname);
+                               sleep(3);
+                               (void) fchown(fd, getuid(),
+                                   getentry(quotagroup, GRPQUOTA));
+                               (void) fchmod(fd, 0640);
+                       }
+                       lseek(fd, (long)(id * sizeof(struct dqblk)), L_SET);
+                       switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
+                       case 0:                 /* EOF */
+                               /*
+                                * Convert implicit 0 quota (EOF)
+                                * into an explicit one (zero'ed dqblk)
+                                */
+                               bzero((caddr_t)&qup->dqblk,
+                                   sizeof(struct dqblk));
+                               break;
+
+                       case sizeof(struct dqblk):      /* OK */
+                               break;
+
+                       default:                /* ERROR */
+                               fprintf(stderr, "edquota: read error in ");
+                               perror(qfpathname);
+                               close(fd);
+                               free(qup);
+                               continue;
+                       }
+                       close(fd);
+               }
+               strcpy(qup->qfname, qfpathname);
+               strcpy(qup->fsname, fs->fs_file);
+               if (quphead == NULL)
+                       quphead = qup;
+               else
+                       quptail->next = qup;
+               quptail = qup;
+               qup->next = 0;
        }
        }
-       return (uid);
+       endfsent();
+       return (quphead);
 }
 
 }
 
-editit()
+/*
+ * Store the requested quota information.
+ */
+putprivs(id, quotatype, quplist)
+       long id;
+       int quotatype;
+       struct quotause *quplist;
 {
 {
-       register pid, xpid;
-       int stat;
+       register struct quotause *qup;
+       int qcmd, fd;
 
 
-       sighold(SIGINT);
-       sighold(SIGQUIT);
-       sighold(SIGHUP);
+       qcmd = QCMD(Q_SETQUOTA, quotatype);
+       for (qup = quplist; qup; qup = qup->next) {
+               if (quotactl(qup->fsname, qcmd, id, &qup->dqblk) == 0)
+                       continue;
+               if ((fd = open(qup->qfname, O_WRONLY)) < 0) {
+                       perror(qup->qfname);
+               } else {
+                       lseek(fd, (long)id * (long)sizeof (struct dqblk), 0);
+                       if (write(fd, &qup->dqblk, sizeof (struct dqblk)) !=
+                           sizeof (struct dqblk)) {
+                               fprintf(stderr, "edquota: ");
+                               perror(qup->qfname);
+                       }
+                       close(fd);
+               }
+       }
+}
 
 
+/*
+ * Take a list of priviledges and get it edited.
+ */
+editit(tmpfile)
+       char *tmpfile;
+{
+       long omask;
+       int pid, stat;
+       extern char *getenv();
+
+       omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
  top:
        if ((pid = fork()) < 0) {
                extern errno;
  top:
        if ((pid = fork()) < 0) {
                extern errno;
@@ -126,186 +316,333 @@ editit()
        if (pid == 0) {
                register char *ed;
 
        if (pid == 0) {
                register char *ed;
 
-               sigrelse(SIGINT);
-               sigrelse(SIGQUIT);
-               sigrelse(SIGHUP);
+               sigsetmask(omask);
                setgid(getgid());
                setuid(getuid());
                setgid(getgid());
                setuid(getuid());
-
                if ((ed = getenv("EDITOR")) == (char *)0)
                if ((ed = getenv("EDITOR")) == (char *)0)
-                       ed = DEFEDITOR;
-               execlp(ed, ed, tmpfil, 0);
+                       ed = _PATH_VI;
+               execlp(ed, ed, tmpfile, 0);
                perror(ed);
                exit(1);
        }
                perror(ed);
                exit(1);
        }
-       while ((xpid = wait(&stat)) >= 0)
-               if (xpid == pid)
-                       break;
-       sigrelse(SIGINT);
-       sigrelse(SIGQUIT);
-       sigrelse(SIGHUP);
-       return (!stat);
+       waitpid(pid, &stat, 0);
+       sigsetmask(omask);
+       if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0)
+               return (0);
+       return (1);
 }
 
 }
 
-getprivs(uid)
-       register uid;
+/*
+ * Convert a quotause list to an ASCII file.
+ */
+writeprivs(quplist, outfd, name, quotatype)
+       struct quotause *quplist;
+       int outfd;
+       char *name;
+       int quotatype;
 {
 {
-       register i;
+       register struct quotause *qup;
        FILE *fd;
 
        FILE *fd;
 
-       getdiscq(uid, dq, dqf);
-       for (i = 0; i < NMOUNT; i++) {
-               odq[i] = dq[i];
-               strcpy(odqf[i], dqf[i]);
-       }
-       if ((fd = fopen(tmpfil, "w")) == NULL) {
+       ftruncate(outfd, 0);
+       lseek(outfd, 0, L_SET);
+       if ((fd = fdopen(dup(outfd), "w")) == NULL) {
                fprintf(stderr, "edquota: ");
                perror(tmpfil);
                exit(1);
        }
                fprintf(stderr, "edquota: ");
                perror(tmpfil);
                exit(1);
        }
-       for (i = 0; i < NMOUNT; i++) {
-               if (*dqf[i] == '\0')
-                       continue;
-               fprintf(fd,
-"fs %s blocks (soft = %d, hard = %d) inodes (soft = %d, hard = %d)\n"
-                       , dqf[i]
-                       , dq[i].dq_bsoftlimit / btodb(1024)
-                       , dq[i].dq_bhardlimit / btodb(1024)
-                       , dq[i].dq_isoftlimit
-                       , dq[i].dq_ihardlimit
-               );
+       fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
+       for (qup = quplist; qup; qup = qup->next) {
+               fprintf(fd, "%s: %s %d, limits (soft = %d, hard = %d)\n",
+                   qup->fsname, "blocks in use:",
+                   dbtob(qup->dqblk.dqb_curblocks) / 1024,
+                   dbtob(qup->dqblk.dqb_bsoftlimit) / 1024,
+                   dbtob(qup->dqblk.dqb_bhardlimit) / 1024);
+               fprintf(fd, "%s %d, limits (soft = %d, hard = %d)\n",
+                   "\tinodes in use:", qup->dqblk.dqb_curinodes,
+                   qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit);
        }
        fclose(fd);
        }
        fclose(fd);
+       return (1);
 }
 
 }
 
-putprivs(uid)
-       register uid;
+/*
+ * Merge changes to an ASCII file into a quotause list.
+ */
+readprivs(quplist, infd)
+       struct quotause *quplist;
+       int infd;
 {
 {
-       register i, j;
-       int n;
+       register struct quotause *qup;
        FILE *fd;
        FILE *fd;
-       char line[BUFSIZ];
+       int cnt;
+       register char *cp;
+       struct dqblk dqblk;
+       char *fsp, line1[BUFSIZ], line2[BUFSIZ];
 
 
-       fd = fopen(tmpfil, "r");
+       lseek(infd, 0, L_SET);
+       fd = fdopen(dup(infd), "r");
        if (fd == NULL) {
                fprintf(stderr, "Can't re-read temp file!!\n");
        if (fd == NULL) {
                fprintf(stderr, "Can't re-read temp file!!\n");
-               return;
+               return (0);
        }
        }
-       for (i = 0; i < NMOUNT; i++) {
-               char *cp, *dp, *next();
-
-               if (fgets(line, sizeof (line), fd) == NULL)
-                       break;
-               cp = next(line, " \t");
-               if (cp == NULL)
-                       break;
-               *cp++ = '\0';
-               while (*cp && *cp == '\t' && *cp == ' ')
-                       cp++;
-               dp = cp, cp = next(cp, " \t");
-               if (cp == NULL)
+       /*
+        * Discard title line, then read pairs of lines to process.
+        */
+       (void) fgets(line1, sizeof (line1), fd);
+       while (fgets(line1, sizeof (line1), fd) != NULL &&
+              fgets(line2, sizeof (line2), fd) != NULL) {
+               if ((fsp = strtok(line1, " \t:")) == NULL) {
+                       fprintf(stderr, "%s: bad format\n", line1);
+                       return (0);
+               }
+               if ((cp = strtok((char *)0, "\n")) == NULL) {
+                       fprintf(stderr, "%s: %s: bad format\n", fsp,
+                           &fsp[strlen(fsp) + 1]);
+                       return (0);
+               }
+               cnt = sscanf(cp,
+                   " blocks in use: %d, limits (soft = %d, hard = %d)",
+                   &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit,
+                   &dqblk.dqb_bhardlimit);
+               if (cnt != 3) {
+                       fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
+                       return (0);
+               }
+               dqblk.dqb_curblocks = btodb(dqblk.dqb_curblocks * 1024);
+               dqblk.dqb_bsoftlimit = btodb(dqblk.dqb_bsoftlimit * 1024);
+               dqblk.dqb_bhardlimit = btodb(dqblk.dqb_bhardlimit * 1024);
+               if ((cp = strtok(line2, "\n")) == NULL) {
+                       fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
+                       return (0);
+               }
+               cnt = sscanf(cp,
+                   "\tinodes in use: %d, limits (soft = %d, hard = %d)",
+                   &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit,
+                   &dqblk.dqb_ihardlimit);
+               if (cnt != 3) {
+                       fprintf(stderr, "%s: %s: bad format\n", fsp, line2);
+                       return (0);
+               }
+               for (qup = quplist; qup; qup = qup->next) {
+                       if (strcmp(fsp, qup->fsname))
+                               continue;
+                       /*
+                        * Cause time limit to be reset when the quota
+                        * is next used if previously had no soft limit
+                        * or were under it, but now have a soft limit
+                        * and are over it.
+                        */
+                       if (dqblk.dqb_bsoftlimit &&
+                           qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
+                           (qup->dqblk.dqb_bsoftlimit == 0 ||
+                            qup->dqblk.dqb_curblocks <
+                            qup->dqblk.dqb_bsoftlimit))
+                               qup->dqblk.dqb_btime = 0;
+                       if (dqblk.dqb_isoftlimit &&
+                           qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
+                           (qup->dqblk.dqb_isoftlimit == 0 ||
+                            qup->dqblk.dqb_curinodes <
+                            qup->dqblk.dqb_isoftlimit))
+                               qup->dqblk.dqb_itime = 0;
+                       qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
+                       qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
+                       qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
+                       qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
+                       qup->flags |= FOUND;
+                       if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
+                           dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes)
+                               break;
+                       fprintf(stderr,
+                           "%s: cannot change current allocation\n", fsp);
                        break;
                        break;
-               *cp++ = '\0';
-               while (*cp && *cp == '\t' && *cp == ' ')
-                       cp++;
-               strcpy(dqf[i], dp);
-               n = sscanf(cp,
-"blocks (soft = %d, hard = %d) inodes (soft = %hd, hard = %hd)\n"
-                       , &dq[i].dq_bsoftlimit
-                       , &dq[i].dq_bhardlimit
-                       , &dq[i].dq_isoftlimit
-                       , &dq[i].dq_ihardlimit
-               );
-               if (n != 4) {
-                       fprintf(stderr, "%s: bad format\n", cp);
-                       continue;
                }
                }
-               dq[i].dq_bsoftlimit *= btodb(1024);
-               dq[i].dq_bhardlimit *= btodb(1024);
        }
        fclose(fd);
        }
        fclose(fd);
-       n = i;
-       for (i = 0; i < n; i++) {
-               if (*dqf[i] == '\0')
-                       break;
-               for (j = 0; j < NMOUNT; j++) {
-                       if (strcmp(dqf[i], odqf[j]) == 0)
-                               break;
-               }
-               if (j >= NMOUNT)
-                       continue;
-               *odqf[j] = '\0';
-               if (dq[i].dq_isoftlimit == odq[j].dq_isoftlimit &&
-                   dq[i].dq_ihardlimit == odq[j].dq_ihardlimit &&
-                   dq[i].dq_bsoftlimit == odq[j].dq_bsoftlimit &&
-                   dq[i].dq_bhardlimit == odq[j].dq_bhardlimit) {
-                       for (j = i; j < NMOUNT; j++) {
-                               dq[j] = dq[j+1];
-                               strcpy(dqf[j], dqf[j+1]);
-                       }
-                       *dqf[j] = '\0';
-                       i--;
+       /*
+        * Disable quotas for any filesystems that have not been found.
+        */
+       for (qup = quplist; qup; qup = qup->next) {
+               if (qup->flags & FOUND) {
+                       qup->flags &= ~FOUND;
                        continue;
                }
                        continue;
                }
-               /*
-                * This isn't really good enough, it is quite likely
-                * to have changed while we have been away editing,
-                * but it's not important enough to worry about at
-                * the minute.
-                */
-               dq[i].dq_curblocks = odq[j].dq_curblocks;
-               dq[i].dq_curinodes = odq[j].dq_curinodes;
-               /*
-                * If we've upped the inode or disk block limits
-                * and the guy is out of warnings, reinitialize.
-                */
-               if (dq[i].dq_bsoftlimit > odq[j].dq_bsoftlimit &&
-                   dq[i].dq_bwarn == 0)
-                       dq[i].dq_bwarn = MAX_DQ_WARN;
-               if (dq[i].dq_isoftlimit > odq[j].dq_isoftlimit &&
-                   dq[i].dq_iwarn == 0)
-                       dq[i].dq_iwarn = MAX_IQ_WARN;
+               qup->dqblk.dqb_bsoftlimit = 0;
+               qup->dqblk.dqb_bhardlimit = 0;
+               qup->dqblk.dqb_isoftlimit = 0;
+               qup->dqblk.dqb_ihardlimit = 0;
        }
        }
-       if (i < NMOUNT) {
-               for (j = 0; j < NMOUNT; j++) {
-                       if (*odqf[j] == '\0')
+       return (1);
+}
+
+/*
+ * Convert a quotause list to an ASCII file of grace times.
+ */
+writetimes(quplist, outfd, quotatype)
+       struct quotause *quplist;
+       int outfd;
+       int quotatype;
+{
+       register struct quotause *qup;
+       char *cvtstoa();
+       FILE *fd;
+
+       ftruncate(outfd, 0);
+       lseek(outfd, 0, L_SET);
+       if ((fd = fdopen(dup(outfd), "w")) == NULL) {
+               fprintf(stderr, "edquota: ");
+               perror(tmpfil);
+               exit(1);
+       }
+       fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
+       fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
+           qfextension[quotatype]);
+       for (qup = quplist; qup; qup = qup->next) {
+               fprintf(fd, "%s: block grace period: %s, ",
+                   qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
+               fprintf(fd, "file grace period: %s\n",
+                   cvtstoa(qup->dqblk.dqb_itime));
+       }
+       fclose(fd);
+       return (1);
+}
+
+/*
+ * Merge changes of grace times in an ASCII file into a quotause list.
+ */
+readtimes(quplist, infd)
+       struct quotause *quplist;
+       int infd;
+{
+       register struct quotause *qup;
+       FILE *fd;
+       int cnt;
+       register char *cp;
+       time_t itime, btime, iseconds, bseconds;
+       char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
+
+       lseek(infd, 0, L_SET);
+       fd = fdopen(dup(infd), "r");
+       if (fd == NULL) {
+               fprintf(stderr, "Can't re-read temp file!!\n");
+               return (0);
+       }
+       /*
+        * Discard two title lines, then read lines to process.
+        */
+       (void) fgets(line1, sizeof (line1), fd);
+       (void) fgets(line1, sizeof (line1), fd);
+       while (fgets(line1, sizeof (line1), fd) != NULL) {
+               if ((fsp = strtok(line1, " \t:")) == NULL) {
+                       fprintf(stderr, "%s: bad format\n", line1);
+                       return (0);
+               }
+               if ((cp = strtok((char *)0, "\n")) == NULL) {
+                       fprintf(stderr, "%s: %s: bad format\n", fsp,
+                           &fsp[strlen(fsp) + 1]);
+                       return (0);
+               }
+               cnt = sscanf(cp,
+                   " block grace period: %d %s file grace period: %d %s",
+                   &btime, bunits, &itime, iunits);
+               if (cnt != 4) {
+                       fprintf(stderr, "%s:%s: bad format\n", fsp, cp);
+                       return (0);
+               }
+               if (cvtatos(btime, bunits, &bseconds) == 0)
+                       return (0);
+               if (cvtatos(itime, iunits, &iseconds) == 0)
+                       return (0);
+               for (qup = quplist; qup; qup = qup->next) {
+                       if (strcmp(fsp, qup->fsname))
                                continue;
                                continue;
-                       strcpy(dqf[i], odqf[j]);
-                       dq[i].dq_isoftlimit = 0;
-                       dq[i].dq_ihardlimit = 0;
-                       dq[i].dq_bsoftlimit = 0;
-                       dq[i].dq_bhardlimit = 0;
-                       /*
-                        * Same applies as just above
-                        * but matters not at all, as we are just
-                        * turning quota'ing off for this filesys.
-                        */
-                       dq[i].dq_curblocks = odq[j].dq_curblocks;
-                       dq[i].dq_curinodes = odq[j].dq_curinodes;
-                       if (++i >= NMOUNT)
-                               break;
+                       qup->dqblk.dqb_btime = bseconds;
+                       qup->dqblk.dqb_itime = iseconds;
+                       qup->flags |= FOUND;
+                       break;
                }
        }
                }
        }
-       if (*dqf[0])
-               putdiscq(uid, dq, dqf);
+       fclose(fd);
+       /*
+        * reset default grace periods for any filesystems
+        * that have not been found.
+        */
+       for (qup = quplist; qup; qup = qup->next) {
+               if (qup->flags & FOUND) {
+                       qup->flags &= ~FOUND;
+                       continue;
+               }
+               qup->dqblk.dqb_btime = 0;
+               qup->dqblk.dqb_itime = 0;
+       }
+       return (1);
 }
 
 }
 
+/*
+ * Convert seconds to ASCII times.
+ */
 char *
 char *
-next(cp, match)
-       register char *cp;
-       char *match;
+cvtstoa(time)
+       time_t time;
+{
+       static char buf[20];
+
+       if (time % (24 * 60 * 60) == 0) {
+               time /= 24 * 60 * 60;
+               sprintf(buf, "%d day%s", time, time == 1 ? "" : "s");
+       } else if (time % (60 * 60) == 0) {
+               time /= 60 * 60;
+               sprintf(buf, "%d hour%s", time, time == 1 ? "" : "s");
+       } else if (time % 60 == 0) {
+               time /= 60;
+               sprintf(buf, "%d minute%s", time, time == 1 ? "" : "s");
+       } else
+               sprintf(buf, "%d second%s", time, time == 1 ? "" : "s");
+       return (buf);
+}
+
+/*
+ * Convert ASCII input times to seconds.
+ */
+cvtatos(time, units, seconds)
+       time_t time;
+       char *units;
+       time_t *seconds;
+{
+
+       if (bcmp(units, "second", 6) == 0)
+               *seconds = time;
+       else if (bcmp(units, "minute", 6) == 0)
+               *seconds = time * 60;
+       else if (bcmp(units, "hour", 4) == 0)
+               *seconds = time * 60 * 60;
+       else if (bcmp(units, "day", 3) == 0)
+               *seconds = time * 24 * 60 * 60;
+       else {
+               printf("%s: bad units, specify %s\n", units,
+                   "days, hours, minutes, or seconds");
+               return (0);
+       }
+       return (1);
+}
+
+/*
+ * Free a list of quotause structures.
+ */
+freeprivs(quplist)
+       struct quotause *quplist;
 {
 {
-       register char *dp;
+       register struct quotause *qup, *nextqup;
 
 
-       while (cp && *cp) {
-               for (dp = match; dp && *dp; dp++)
-                       if (*dp == *cp)
-                               return (cp);
-               cp++;
+       for (qup = quplist; qup; qup = nextqup) {
+               nextqup = qup->next;
+               free(qup);
        }
        }
-       return ((char *)0);
 }
 
 }
 
+/*
+ * Check whether a string is completely composed of digits.
+ */
 alldigits(s)
        register char *s;
 {
 alldigits(s)
        register char *s;
 {
@@ -319,77 +656,40 @@ alldigits(s)
        return (1);
 }
 
        return (1);
 }
 
-getdiscq(uid, dq, dqf)
-       register uid;
-       register struct dquot *dq;
-       register char (*dqf)[MAXPATHLEN + 1];
-{
+/*
+ * Check to see if a particular quota is to be enabled.
+ */
+hasquota(fs, type, qfnamep)
        register struct fstab *fs;
        register struct fstab *fs;
-       char qfilename[MAXPATHLEN + 1];
-
-       setfsent();
-       while (fs = getfsent()) {
-               struct  stat statb;
-               struct  dqblk dqblk;
-               dev_t   fsdev;
-
-               if (stat(fs->fs_spec, &statb) < 0)
-                       continue;
-               fsdev = statb.st_rdev;
-               sprintf(qfilename, "%s/%s", fs->fs_file, qfname);
-               if (stat(qfilename, &statb) < 0 || statb.st_dev != fsdev)
-                       continue;
-               if (quota(Q_GETDLIM, uid, fsdev, &dqblk) != 0) {
-                       register fd = open(qfilename, O_RDONLY);
+       int type;
+       char **qfnamep;
+{
+       register char *opt;
+       char *cp, *index(), *strtok();
+       static char initname, usrname[100], grpname[100];
+       static char buf[BUFSIZ];
 
 
-                       if (fd < 0)
-                               continue;
-                       lseek(fd, (long)(uid * sizeof dqblk), L_SET);
-                       if (read(fd, &dqblk, sizeof dqblk) != sizeof (dqblk)) {
-                               close(fd);
-                               continue;
-                       }
-                       close(fd);
-               }
-               dq->dq_dqb = dqblk;
-               dq->dq_dev = fsdev;
-               strcpy(*dqf, fs->fs_file);
-               dq++, dqf++;
+       if (!initname) {
+               sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname);
+               sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname);
+               initname = 1;
        }
        }
-       endfsent();
-       **dqf = '\0';
-}
-
-putdiscq(uid, dq, dqf)
-       register uid;
-       register struct dquot *dq;
-       register char (*dqf)[MAXPATHLEN + 1];
-{
-       register fd, cnt;
-       struct stat sb;
-       struct fstab *fs;
-
-       cnt = 0;
-       for (cnt = 0; ++cnt <= NMOUNT && **dqf; dq++, dqf++) {
-               fs = getfsfile(*dqf);
-               if (fs == NULL) {
-                       fprintf(stderr, "%s: not in /etc/fstab\n", *dqf);
-                       continue;
-               }
-               strcat(*dqf, "/");
-               strcat(*dqf, qfname);
-               if (stat(*dqf, &sb) >= 0)
-                       quota(Q_SETDLIM, uid, sb.st_dev, &dq->dq_dqb);
-               if ((fd = open(*dqf, 1)) < 0) {
-                       perror(*dqf);
-               } else {
-                       lseek(fd, (long)uid * (long)sizeof (struct dqblk), 0);
-                       if (write(fd, &dq->dq_dqb, sizeof (struct dqblk)) !=
-                           sizeof (struct dqblk)) {
-                               fprintf(stderr, "edquota: ");
-                               perror(*dqf);
-                       }
-                       close(fd);
-               }
+       strcpy(buf, fs->fs_mntops);
+       for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
+               if (cp = index(opt, '='))
+                       *cp++ = '\0';
+               if (type == USRQUOTA && strcmp(opt, usrname) == 0)
+                       break;
+               if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
+                       break;
        }
        }
+       if (!opt)
+               return (0);
+       if (cp) {
+               *qfnamep = cp;
+               return (1);
+       }
+       (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]);
+       *qfnamep = buf;
+       return (1);
 }
 }