]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.sbin/edquota/edquota.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.sbin / edquota / edquota.c
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  * 4. 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 #if 0
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1980, 1990, 1993\n\
37         The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39
40 #ifndef lint
41 static char sccsid[] = "@(#)edquota.c   8.1 (Berkeley) 6/6/93";
42 #endif /* not lint */
43 #endif
44
45 #include <sys/cdefs.h>
46 __FBSDID("$FreeBSD$");
47
48 /*
49  * Disk quota editor.
50  */
51
52 #include <sys/file.h>
53 #include <sys/mount.h>
54 #include <sys/wait.h>
55 #include <ufs/ufs/quota.h>
56
57 #include <ctype.h>
58 #include <err.h>
59 #include <errno.h>
60 #include <fstab.h>
61 #include <grp.h>
62 #include <inttypes.h>
63 #include <libutil.h>
64 #include <pwd.h>
65 #include <signal.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <unistd.h>
70
71 #include "pathnames.h"
72
73 /* Let's be paranoid about block size */
74 #if 10 > DEV_BSHIFT
75 #define dbtokb(db) \
76         ((off_t)(db) >> (10-DEV_BSHIFT))
77 #elif 10 < DEV_BSHIFT
78 #define dbtokb(db) \
79         ((off_t)(db) << (DEV_BSHIFT-10))
80 #else
81 #define dbtokb(db)      (db)
82 #endif
83
84 const char *qfextension[] = INITQFNAMES;
85 char tmpfil[] = _PATH_TMP;
86 int hflag;
87
88 struct quotause {
89         struct  quotause *next;
90         struct  quotafile *qf;
91         struct  dqblk dqblk;
92         int     flags;
93         char    fsname[MAXPATHLEN + 1];
94 };
95 #define FOUND   0x01
96
97 int alldigits(const char *s);
98 int cvtatos(uint64_t, char *, uint64_t *);
99 char *cvtstoa(uint64_t);
100 uint64_t cvtblkval(uint64_t, char, const char *);
101 uint64_t cvtinoval(uint64_t, char, const char *);
102 int editit(char *);
103 char *fmthumanvalblks(int64_t);
104 char *fmthumanvalinos(int64_t);
105 void freeprivs(struct quotause *);
106 int getentry(const char *, int);
107 struct quotause *getprivs(long, int, char *);
108 void putprivs(long, struct quotause *);
109 int readprivs(struct quotause *, char *);
110 int readtimes(struct quotause *, char *);
111 static void usage(void);
112 int writetimes(struct quotause *, int, int);
113 int writeprivs(struct quotause *, int, char *, int);
114
115 int
116 main(int argc, char *argv[])
117 {
118         struct quotause *qup, *protoprivs, *curprivs;
119         long id, protoid;
120         int i, quotatype, range, tmpfd;
121         uid_t startuid, enduid;
122         uint64_t lim;
123         char *protoname, *cp, *endpt, *oldoptarg;
124         int eflag = 0, tflag = 0, pflag = 0, ch;
125         char *fspath = NULL;
126         char buf[MAXLOGNAME];
127
128         if (argc < 2)
129                 usage();
130         if (getuid())
131                 errx(1, "permission denied");
132         quotatype = USRQUOTA;
133         protoprivs = NULL;
134         curprivs = NULL;
135         protoname = NULL;
136         while ((ch = getopt(argc, argv, "ughtf:p:e:")) != -1) {
137                 switch(ch) {
138                 case 'f':
139                         fspath = optarg;
140                         break;
141                 case 'p':
142                         if (eflag) {
143                                 warnx("cannot specify both -e and -p");
144                                 usage();
145                                 /* not reached */
146                         }
147                         protoname = optarg;
148                         pflag++;
149                         break;
150                 case 'g':
151                         quotatype = GRPQUOTA;
152                         break;
153                 case 'h':
154                         hflag++;
155                         break;
156                 case 'u':
157                         quotatype = USRQUOTA;
158                         break;
159                 case 't':
160                         tflag++;
161                         break;
162                 case 'e':
163                         if (pflag) {
164                                 warnx("cannot specify both -e and -p");
165                                 usage();
166                                 /* not reached */
167                         }
168                         if ((qup = calloc(1, sizeof(*qup))) == NULL)
169                                 errx(2, "out of memory");
170                         oldoptarg = optarg;
171                         for (i = 0, cp = optarg;
172                              (cp = strsep(&optarg, ":")) != NULL; i++) {
173                                 if (cp != oldoptarg)
174                                         *(cp - 1) = ':';
175                                 if (i > 0 && !isdigit(*cp)) {
176                                         warnx("incorrect quota specification: "
177                                             "%s", oldoptarg);
178                                         usage();
179                                         /* Not Reached */
180                                 }
181                                 switch (i) {
182                                 case 0:
183                                         strlcpy(qup->fsname, cp,
184                                             sizeof(qup->fsname));
185                                         break;
186                                 case 1:
187                                         lim = strtoll(cp, &endpt, 10);
188                                         qup->dqblk.dqb_bsoftlimit =
189                                                 cvtblkval(lim, *endpt,
190                                                     "block soft limit");
191                                         continue;
192                                 case 2:
193                                         lim = strtoll(cp, &endpt, 10);
194                                         qup->dqblk.dqb_bhardlimit =
195                                                 cvtblkval(lim, *endpt,
196                                                     "block hard limit");
197                                         continue;
198                                 case 3:
199                                         lim = strtoll(cp, &endpt, 10);
200                                         qup->dqblk.dqb_isoftlimit =
201                                                 cvtinoval(lim, *endpt,
202                                                     "inode soft limit");
203                                         continue;
204                                 case 4:
205                                         lim = strtoll(cp, &endpt, 10);
206                                         qup->dqblk.dqb_ihardlimit =
207                                                 cvtinoval(lim, *endpt,
208                                                     "inode hard limit");
209                                         continue;
210                                 default:
211                                         warnx("incorrect quota specification: "
212                                             "%s", oldoptarg);
213                                         usage();
214                                         /* Not Reached */
215                                 }
216                         }
217                         if (protoprivs == NULL) {
218                                 protoprivs = curprivs = qup;
219                         } else {
220                                 curprivs->next = qup;
221                                 curprivs = qup;
222                         }
223                         eflag++;
224                         break;
225                 default:
226                         usage();
227                         /* Not Reached */
228                 }
229         }
230         argc -= optind;
231         argv += optind;
232         if (pflag || eflag) {
233                 if (pflag) {
234                         if ((protoid = getentry(protoname, quotatype)) == -1)
235                                 exit(1);
236                         protoprivs = getprivs(protoid, quotatype, fspath);
237                         if (protoprivs == NULL)
238                                 exit(0);
239                         for (qup = protoprivs; qup; qup = qup->next) {
240                                 qup->dqblk.dqb_btime = 0;
241                                 qup->dqblk.dqb_itime = 0;
242                         }
243                 }
244                 for (; argc-- > 0; argv++) {
245                         if (strspn(*argv, "0123456789-") == strlen(*argv) &&
246                             (cp = strchr(*argv, '-')) != NULL) {
247                                 *cp++ = '\0';
248                                 startuid = atoi(*argv);
249                                 enduid = atoi(cp);
250                                 if (enduid < startuid)
251                                         errx(1,
252         "ending uid (%d) must be >= starting uid (%d) when using uid ranges",
253                                                 enduid, startuid);
254                                 range = 1;
255                         } else {
256                                 startuid = enduid = 0;
257                                 range = 0;
258                         }
259                         for ( ; startuid <= enduid; startuid++) {
260                                 if (range)
261                                         snprintf(buf, sizeof(buf), "%d",
262                                             startuid);
263                                 else
264                                         snprintf(buf, sizeof(buf), "%s",
265                                                 *argv);
266                                 if ((id = getentry(buf, quotatype)) < 0)
267                                         continue;
268                                 if (pflag) {
269                                         putprivs(id, protoprivs);
270                                         continue;
271                                 }
272                                 for (qup = protoprivs; qup; qup = qup->next) {
273                                         curprivs = getprivs(id, quotatype,
274                                             qup->fsname);
275                                         if (curprivs == NULL)
276                                                 continue;
277                                         curprivs->dqblk = qup->dqblk;
278                                         putprivs(id, curprivs);
279                                         freeprivs(curprivs);
280                                 }
281                         }
282                 }
283                 if (pflag)
284                         freeprivs(protoprivs);
285                 exit(0);
286         }
287         tmpfd = mkstemp(tmpfil);
288         fchown(tmpfd, getuid(), getgid());
289         if (tflag) {
290                 if ((protoprivs = getprivs(0, quotatype, fspath)) != NULL) {
291                         if (writetimes(protoprivs, tmpfd, quotatype) != 0 &&
292                             editit(tmpfil) && readtimes(protoprivs, tmpfil))
293                                 putprivs(0L, protoprivs);
294                         freeprivs(protoprivs);
295                 }
296                 close(tmpfd);
297                 unlink(tmpfil);
298                 exit(0);
299         }
300         for ( ; argc > 0; argc--, argv++) {
301                 if ((id = getentry(*argv, quotatype)) == -1)
302                         continue;
303                 if ((curprivs = getprivs(id, quotatype, fspath)) == NULL)
304                         exit(1);
305                 if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0)
306                         continue;
307                 if (editit(tmpfil) && readprivs(curprivs, tmpfil))
308                         putprivs(id, curprivs);
309                 freeprivs(curprivs);
310         }
311         close(tmpfd);
312         unlink(tmpfil);
313         exit(0);
314 }
315
316 static void
317 usage(void)
318 {
319         fprintf(stderr, "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
320                 "usage: edquota [-uh] [-f fspath] [-p username] username ...",
321                 "       edquota [-u] -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
322                 "               username ...",
323                 "       edquota -g [-h] [-f fspath] [-p groupname] groupname ...",
324                 "       edquota -g -e fspath[:bslim[:bhlim[:islim[:ihlim]]]] [-e ...]",
325                 "               groupname ...",
326                 "       edquota [-u] -t [-f fspath]",
327                 "       edquota -g -t [-f fspath]");
328         exit(1);
329 }
330
331 /*
332  * This routine converts a name for a particular quota type to
333  * an identifier. This routine must agree with the kernel routine
334  * getinoquota as to the interpretation of quota types.
335  */
336 int
337 getentry(const char *name, int quotatype)
338 {
339         struct passwd *pw;
340         struct group *gr;
341
342         if (alldigits(name))
343                 return (atoi(name));
344         switch(quotatype) {
345         case USRQUOTA:
346                 if ((pw = getpwnam(name)))
347                         return (pw->pw_uid);
348                 warnx("%s: no such user", name);
349                 sleep(3);
350                 break;
351         case GRPQUOTA:
352                 if ((gr = getgrnam(name)))
353                         return (gr->gr_gid);
354                 warnx("%s: no such group", name);
355                 sleep(3);
356                 break;
357         default:
358                 warnx("%d: unknown quota type", quotatype);
359                 sleep(3);
360                 break;
361         }
362         sleep(1);
363         return (-1);
364 }
365
366 /*
367  * Collect the requested quota information.
368  */
369 struct quotause *
370 getprivs(long id, int quotatype, char *fspath)
371 {
372         struct quotafile *qf;
373         struct fstab *fs;
374         struct quotause *qup, *quptail;
375         struct quotause *quphead;
376
377         setfsent();
378         quphead = quptail = NULL;
379         while ((fs = getfsent())) {
380                 if (fspath && *fspath && strcmp(fspath, fs->fs_spec) &&
381                     strcmp(fspath, fs->fs_file))
382                         continue;
383                 if (strcmp(fs->fs_vfstype, "ufs"))
384                         continue;
385                 if ((qf = quota_open(fs, quotatype, O_CREAT|O_RDWR)) == NULL) {
386                         if (errno != EOPNOTSUPP)
387                                 warn("cannot open quotas on %s", fs->fs_file);
388                         continue;
389                 }
390                 if ((qup = (struct quotause *)calloc(1, sizeof(*qup))) == NULL)
391                         errx(2, "out of memory");
392                 qup->qf = qf;
393                 strncpy(qup->fsname, fs->fs_file, sizeof(qup->fsname));
394                 if (quota_read(qf, &qup->dqblk, id) == -1) {
395                         warn("cannot read quotas on %s", fs->fs_file);
396                         freeprivs(qup);
397                         continue;
398                 }
399                 if (quphead == NULL)
400                         quphead = qup;
401                 else
402                         quptail->next = qup;
403                 quptail = qup;
404                 qup->next = 0;
405         }
406         if (quphead == NULL) {
407                 warnx("No quotas on %s", fspath ? fspath : "any filesystems");
408         }
409         endfsent();
410         return (quphead);
411 }
412
413 /*
414  * Store the requested quota information.
415  */
416 void
417 putprivs(long id, struct quotause *quplist)
418 {
419         struct quotause *qup;
420
421         for (qup = quplist; qup; qup = qup->next)
422                 if (quota_write_limits(qup->qf, &qup->dqblk, id) == -1)
423                         warn("%s", qup->fsname);
424 }
425
426 /*
427  * Take a list of privileges and get it edited.
428  */
429 int
430 editit(char *tmpf)
431 {
432         long omask;
433         int pid, status;
434
435         omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
436  top:
437         if ((pid = fork()) < 0) {
438
439                 if (errno == EPROCLIM) {
440                         warnx("you have too many processes");
441                         return(0);
442                 }
443                 if (errno == EAGAIN) {
444                         sleep(1);
445                         goto top;
446                 }
447                 warn("fork");
448                 return (0);
449         }
450         if (pid == 0) {
451                 const char *ed;
452
453                 sigsetmask(omask);
454                 if (setgid(getgid()) != 0)
455                         err(1, "setgid failed");
456                 if (setuid(getuid()) != 0)
457                         err(1, "setuid failed");
458                 if ((ed = getenv("EDITOR")) == (char *)0)
459                         ed = _PATH_VI;
460                 execlp(ed, ed, tmpf, (char *)0);
461                 err(1, "%s", ed);
462         }
463         waitpid(pid, &status, 0);
464         sigsetmask(omask);
465         if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
466                 return (0);
467         return (1);
468 }
469
470 /*
471  * Convert a quotause list to an ASCII file.
472  */
473 int
474 writeprivs(struct quotause *quplist, int outfd, char *name, int quotatype)
475 {
476         struct quotause *qup;
477         FILE *fd;
478
479         ftruncate(outfd, 0);
480         lseek(outfd, 0, L_SET);
481         if ((fd = fdopen(dup(outfd), "w")) == NULL)
482                 err(1, "%s", tmpfil);
483         fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name);
484         for (qup = quplist; qup; qup = qup->next) {
485                 fprintf(fd, "%s: in use: %s, ", qup->fsname,
486                     fmthumanvalblks(qup->dqblk.dqb_curblocks));
487                 fprintf(fd, "limits (soft = %s, ",
488                     fmthumanvalblks(qup->dqblk.dqb_bsoftlimit));
489                 fprintf(fd, "hard = %s)\n",
490                     fmthumanvalblks(qup->dqblk.dqb_bhardlimit));
491                 fprintf(fd, "\tinodes in use: %s, ",
492                     fmthumanvalinos(qup->dqblk.dqb_curinodes));
493                 fprintf(fd, "limits (soft = %s, ",
494                     fmthumanvalinos(qup->dqblk.dqb_isoftlimit));
495                 fprintf(fd, "hard = %s)\n",
496                     fmthumanvalinos(qup->dqblk.dqb_ihardlimit));
497         }
498         fclose(fd);
499         return (1);
500 }
501
502 char *
503 fmthumanvalblks(int64_t blocks)
504 {
505         static char numbuf[20];
506
507         if (hflag) {
508                 humanize_number(numbuf, blocks < 0 ? 7 : 6,
509                     dbtob(blocks), "", HN_AUTOSCALE, HN_NOSPACE);
510                 return (numbuf);
511         }
512         snprintf(numbuf, sizeof(numbuf), "%juk", (uintmax_t)dbtokb(blocks));
513         return(numbuf);
514 }
515
516 char *
517 fmthumanvalinos(int64_t inos)
518 {
519         static char numbuf[20];
520
521         if (hflag) {
522                 humanize_number(numbuf, inos < 0 ? 7 : 6,
523                     inos, "", HN_AUTOSCALE, HN_NOSPACE | HN_DIVISOR_1000);
524                 return (numbuf);
525         }
526         snprintf(numbuf, sizeof(numbuf), "%ju", (uintmax_t)inos);
527         return(numbuf);
528 }
529
530 /*
531  * Merge changes to an ASCII file into a quotause list.
532  */
533 int
534 readprivs(struct quotause *quplist, char *inname)
535 {
536         struct quotause *qup;
537         FILE *fd;
538         uintmax_t hardlimit, softlimit, curitems;
539         char hardunits, softunits, curitemunits;
540         int cnt;
541         char *cp;
542         struct dqblk dqblk;
543         char *fsp, line1[BUFSIZ], line2[BUFSIZ];
544
545         fd = fopen(inname, "r");
546         if (fd == NULL) {
547                 warnx("can't re-read temp file!!");
548                 return (0);
549         }
550         /*
551          * Discard title line, then read pairs of lines to process.
552          */
553         (void) fgets(line1, sizeof (line1), fd);
554         while (fgets(line1, sizeof (line1), fd) != NULL &&
555                fgets(line2, sizeof (line2), fd) != NULL) {
556                 if ((fsp = strtok(line1, " \t:")) == NULL) {
557                         warnx("%s: bad format", line1);
558                         return (0);
559                 }
560                 if ((cp = strtok((char *)0, "\n")) == NULL) {
561                         warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
562                         return (0);
563                 }
564                 cnt = sscanf(cp,
565                     " in use: %ju%c, limits (soft = %ju%c, hard = %ju%c)",
566                     &curitems, &curitemunits, &softlimit, &softunits,
567                     &hardlimit, &hardunits);
568                 /*
569                  * The next three check for old-style input formats.
570                  */
571                 if (cnt != 6)
572                         cnt = sscanf(cp,
573                          " in use: %ju%c, limits (soft = %ju%c hard = %ju%c",
574                             &curitems, &curitemunits, &softlimit,
575                             &softunits, &hardlimit, &hardunits);
576                 if (cnt != 6)
577                         cnt = sscanf(cp,
578                         " in use: %ju%c, limits (soft = %ju%c hard = %ju%c)",
579                             &curitems, &curitemunits, &softlimit,
580                             &softunits, &hardlimit, &hardunits);
581                 if (cnt != 6)
582                         cnt = sscanf(cp,
583                         " in use: %ju%c, limits (soft = %ju%c, hard = %ju%c",
584                             &curitems, &curitemunits, &softlimit,
585                             &softunits, &hardlimit, &hardunits);
586                 if (cnt != 6) {
587                         warnx("%s:%s: bad format", fsp, cp);
588                         return (0);
589                 }
590                 dqblk.dqb_curblocks = cvtblkval(curitems, curitemunits,
591                     "current block count");
592                 dqblk.dqb_bsoftlimit = cvtblkval(softlimit, softunits,
593                     "block soft limit");
594                 dqblk.dqb_bhardlimit = cvtblkval(hardlimit, hardunits,
595                     "block hard limit");
596                 if ((cp = strtok(line2, "\n")) == NULL) {
597                         warnx("%s: %s: bad format", fsp, line2);
598                         return (0);
599                 }
600                 cnt = sscanf(&cp[7],
601                     " in use: %ju%c limits (soft = %ju%c, hard = %ju%c)",
602                     &curitems, &curitemunits, &softlimit,
603                     &softunits, &hardlimit, &hardunits);
604                 /*
605                  * The next three check for old-style input formats.
606                  */
607                 if (cnt != 6)
608                         cnt = sscanf(&cp[7],
609                          " in use: %ju%c limits (soft = %ju%c hard = %ju%c",
610                             &curitems, &curitemunits, &softlimit,
611                             &softunits, &hardlimit, &hardunits);
612                 if (cnt != 6)
613                         cnt = sscanf(&cp[7],
614                         " in use: %ju%c limits (soft = %ju%c hard = %ju%c)",
615                             &curitems, &curitemunits, &softlimit,
616                             &softunits, &hardlimit, &hardunits);
617                 if (cnt != 6)
618                         cnt = sscanf(&cp[7],
619                         " in use: %ju%c limits (soft = %ju%c, hard = %ju%c",
620                             &curitems, &curitemunits, &softlimit,
621                             &softunits, &hardlimit, &hardunits);
622                 if (cnt != 6) {
623                         warnx("%s: %s: bad format cnt %d", fsp, &cp[7], cnt);
624                         return (0);
625                 }
626                 dqblk.dqb_curinodes = cvtinoval(curitems, curitemunits,
627                     "current inode count");
628                 dqblk.dqb_isoftlimit = cvtinoval(softlimit, softunits,
629                     "inode soft limit");
630                 dqblk.dqb_ihardlimit = cvtinoval(hardlimit, hardunits,
631                     "inode hard limit");
632                 for (qup = quplist; qup; qup = qup->next) {
633                         if (strcmp(fsp, qup->fsname))
634                                 continue;
635                         /*
636                          * Cause time limit to be reset when the quota
637                          * is next used if previously had no soft limit
638                          * or were under it, but now have a soft limit
639                          * and are over it.
640                          */
641                         if (dqblk.dqb_bsoftlimit &&
642                             qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit &&
643                             (qup->dqblk.dqb_bsoftlimit == 0 ||
644                              qup->dqblk.dqb_curblocks <
645                              qup->dqblk.dqb_bsoftlimit))
646                                 qup->dqblk.dqb_btime = 0;
647                         if (dqblk.dqb_isoftlimit &&
648                             qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit &&
649                             (qup->dqblk.dqb_isoftlimit == 0 ||
650                              qup->dqblk.dqb_curinodes <
651                              qup->dqblk.dqb_isoftlimit))
652                                 qup->dqblk.dqb_itime = 0;
653                         qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit;
654                         qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit;
655                         qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit;
656                         qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit;
657                         qup->flags |= FOUND;
658                         /* Humanized input returns only approximate counts */
659                         if (hflag ||
660                             (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks &&
661                              dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes))
662                                 break;
663                         warnx("%s: cannot change current allocation", fsp);
664                         break;
665                 }
666         }
667         fclose(fd);
668         /*
669          * Disable quotas for any filesystems that have not been found.
670          */
671         for (qup = quplist; qup; qup = qup->next) {
672                 if (qup->flags & FOUND) {
673                         qup->flags &= ~FOUND;
674                         continue;
675                 }
676                 qup->dqblk.dqb_bsoftlimit = 0;
677                 qup->dqblk.dqb_bhardlimit = 0;
678                 qup->dqblk.dqb_isoftlimit = 0;
679                 qup->dqblk.dqb_ihardlimit = 0;
680         }
681         return (1);
682 }
683
684 /*
685  * Convert a quotause list to an ASCII file of grace times.
686  */
687 int
688 writetimes(struct quotause *quplist, int outfd, int quotatype)
689 {
690         struct quotause *qup;
691         FILE *fd;
692
693         ftruncate(outfd, 0);
694         lseek(outfd, 0, L_SET);
695         if ((fd = fdopen(dup(outfd), "w")) == NULL)
696                 err(1, "%s", tmpfil);
697         fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n");
698         fprintf(fd, "Grace period before enforcing soft limits for %ss:\n",
699             qfextension[quotatype]);
700         for (qup = quplist; qup; qup = qup->next) {
701                 fprintf(fd, "%s: block grace period: %s, ",
702                     qup->fsname, cvtstoa(qup->dqblk.dqb_btime));
703                 fprintf(fd, "file grace period: %s\n",
704                     cvtstoa(qup->dqblk.dqb_itime));
705         }
706         fclose(fd);
707         return (1);
708 }
709
710 /*
711  * Merge changes of grace times in an ASCII file into a quotause list.
712  */
713 int
714 readtimes(struct quotause *quplist, char *inname)
715 {
716         struct quotause *qup;
717         FILE *fd;
718         int cnt;
719         char *cp;
720         uintmax_t itime, btime, iseconds, bseconds;
721         char *fsp, bunits[10], iunits[10], line1[BUFSIZ];
722
723         fd = fopen(inname, "r");
724         if (fd == NULL) {
725                 warnx("can't re-read temp file!!");
726                 return (0);
727         }
728         /*
729          * Discard two title lines, then read lines to process.
730          */
731         (void) fgets(line1, sizeof (line1), fd);
732         (void) fgets(line1, sizeof (line1), fd);
733         while (fgets(line1, sizeof (line1), fd) != NULL) {
734                 if ((fsp = strtok(line1, " \t:")) == NULL) {
735                         warnx("%s: bad format", line1);
736                         return (0);
737                 }
738                 if ((cp = strtok((char *)0, "\n")) == NULL) {
739                         warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]);
740                         return (0);
741                 }
742                 cnt = sscanf(cp,
743                     " block grace period: %ju %s file grace period: %ju %s",
744                     &btime, bunits, &itime, iunits);
745                 if (cnt != 4) {
746                         warnx("%s:%s: bad format", fsp, cp);
747                         return (0);
748                 }
749                 if (cvtatos(btime, bunits, &bseconds) == 0)
750                         return (0);
751                 if (cvtatos(itime, iunits, &iseconds) == 0)
752                         return (0);
753                 for (qup = quplist; qup; qup = qup->next) {
754                         if (strcmp(fsp, qup->fsname))
755                                 continue;
756                         qup->dqblk.dqb_btime = bseconds;
757                         qup->dqblk.dqb_itime = iseconds;
758                         qup->flags |= FOUND;
759                         break;
760                 }
761         }
762         fclose(fd);
763         /*
764          * reset default grace periods for any filesystems
765          * that have not been found.
766          */
767         for (qup = quplist; qup; qup = qup->next) {
768                 if (qup->flags & FOUND) {
769                         qup->flags &= ~FOUND;
770                         continue;
771                 }
772                 qup->dqblk.dqb_btime = 0;
773                 qup->dqblk.dqb_itime = 0;
774         }
775         return (1);
776 }
777
778 /*
779  * Convert seconds to ASCII times.
780  */
781 char *
782 cvtstoa(uint64_t secs)
783 {
784         static char buf[20];
785
786         if (secs % (24 * 60 * 60) == 0) {
787                 secs /= 24 * 60 * 60;
788                 sprintf(buf, "%ju day%s", (uintmax_t)secs,
789                     secs == 1 ? "" : "s");
790         } else if (secs % (60 * 60) == 0) {
791                 secs /= 60 * 60;
792                 sprintf(buf, "%ju hour%s", (uintmax_t)secs,
793                     secs == 1 ? "" : "s");
794         } else if (secs % 60 == 0) {
795                 secs /= 60;
796                 sprintf(buf, "%ju minute%s", (uintmax_t)secs,
797                     secs == 1 ? "" : "s");
798         } else
799                 sprintf(buf, "%ju second%s", (uintmax_t)secs,
800                     secs == 1 ? "" : "s");
801         return (buf);
802 }
803
804 /*
805  * Convert ASCII input times to seconds.
806  */
807 int
808 cvtatos(uint64_t period, char *units, uint64_t *seconds)
809 {
810
811         if (bcmp(units, "second", 6) == 0)
812                 *seconds = period;
813         else if (bcmp(units, "minute", 6) == 0)
814                 *seconds = period * 60;
815         else if (bcmp(units, "hour", 4) == 0)
816                 *seconds = period * 60 * 60;
817         else if (bcmp(units, "day", 3) == 0)
818                 *seconds = period * 24 * 60 * 60;
819         else {
820                 warnx("%s: bad units, specify %s\n", units,
821                     "days, hours, minutes, or seconds");
822                 return (0);
823         }
824         return (1);
825 }
826
827 /*
828  * Convert a limit to number of disk blocks.
829  */
830 uint64_t
831 cvtblkval(uint64_t limit, char units, const char *itemname)
832 {
833
834         switch(units) {
835         case 'B':
836         case 'b':
837                 limit = btodb(limit);
838                 break;
839         case '\0':      /* historic behavior */
840         case ',':       /* historic behavior */
841         case ')':       /* historic behavior */
842         case 'K':
843         case 'k':
844                 limit *= btodb(1024);
845                 break;
846         case 'M':
847         case 'm':
848                 limit *= btodb(1048576);
849                 break;
850         case 'G':
851         case 'g':
852                 limit *= btodb(1073741824);
853                 break;
854         case 'T':
855         case 't':
856                 limit *= btodb(1099511627776);
857                 break;
858         case 'P':
859         case 'p':
860                 limit *= btodb(1125899906842624);
861                 break;
862         case 'E':
863         case 'e':
864                 limit *= btodb(1152921504606846976);
865                 break;
866         case ' ':
867                 errx(2, "No space permitted between value and units for %s\n",
868                     itemname);
869                 break;
870         default:
871                 errx(2, "%ju%c: unknown units for %s, specify "
872                     "none, K, M, G, T, P, or E\n",
873                     (uintmax_t)limit, units, itemname);
874                 break;
875         }
876         return (limit);
877 }
878
879 /*
880  * Convert a limit to number of inodes.
881  */
882 uint64_t
883 cvtinoval(uint64_t limit, char units, const char *itemname)
884 {
885
886         switch(units) {
887         case 'B':
888         case 'b':
889         case '\0':      /* historic behavior */
890         case ',':       /* historic behavior */
891         case ')':       /* historic behavior */
892                 break;
893         case 'K':
894         case 'k':
895                 limit *= 1000;
896                 break;
897         case 'M':
898         case 'm':
899                 limit *= 1000000;
900                 break;
901         case 'G':
902         case 'g':
903                 limit *= 1000000000;
904                 break;
905         case 'T':
906         case 't':
907                 limit *= 1000000000000;
908                 break;
909         case 'P':
910         case 'p':
911                 limit *= 1000000000000000;
912                 break;
913         case 'E':
914         case 'e':
915                 limit *= 1000000000000000000;
916                 break;
917         case ' ':
918                 errx(2, "No space permitted between value and units for %s\n",
919                     itemname);
920                 break;
921         default:
922                 errx(2, "%ju%c: unknown units for %s, specify "
923                     "none, K, M, G, T, P, or E\n",
924                     (uintmax_t)limit, units, itemname);
925                 break;
926         }
927         return (limit);
928 }
929
930 /*
931  * Free a list of quotause structures.
932  */
933 void
934 freeprivs(struct quotause *quplist)
935 {
936         struct quotause *qup, *nextqup;
937
938         for (qup = quplist; qup; qup = nextqup) {
939                 quota_close(qup->qf);
940                 nextqup = qup->next;
941                 free(qup);
942         }
943 }
944
945 /*
946  * Check whether a string is completely composed of digits.
947  */
948 int
949 alldigits(const char *s)
950 {
951         int c;
952
953         c = *s++;
954         do {
955                 if (!isdigit(c))
956                         return (0);
957         } while ((c = *s++));
958         return (1);
959 }