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