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