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 #if 0
36 #ifndef lint
37 static const char copyright[] =
38 "@(#) Copyright (c) 1980, 1990, 1993\n\
39 The Regents of the University of California. All rights reserved.\n";
40 #endif /* not lint */
41
42 #ifndef lint
43 static char sccsid[] = "@(#)edquota.c 8.1 (Berkeley) 6/6/93";
44 #endif /* not lint */
45 #endif
46
47 #include <sys/cdefs.h>
48 __FBSDID("$FreeBSD$");
49
50 /*
51 * Disk quota editor.
52 */
53
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 static const char *qfextension[] = INITQFNAMES;
87 static char tmpfil[] = _PATH_TMP;
88 static 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
main(int argc,char * argv[])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 = mkostemp(tmpfil, O_CLOEXEC);
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
usage(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
getentry(const char * name,int quotatype)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 *
getprivs(long id,int quotatype,char * fspath)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 strlcpy(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
putprivs(long id,struct quotause * quplist)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 privileges and get it edited.
430 */
431 int
editit(char * tmpf)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
writeprivs(struct quotause * quplist,int outfd,char * name,int quotatype)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 *
fmthumanvalblks(int64_t blocks)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 *
fmthumanvalinos(int64_t inos)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
readprivs(struct quotause * quplist,char * inname)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
writetimes(struct quotause * quplist,int outfd,int quotatype)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
readtimes(struct quotause * quplist,char * inname)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 *
cvtstoa(uint64_t secs)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
cvtatos(uint64_t period,char * units,uint64_t * seconds)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
cvtblkval(uint64_t limit,char units,const char * itemname)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
cvtinoval(uint64_t limit,char units,const char * itemname)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
freeprivs(struct quotause * quplist)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
alldigits(const char * s)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 }
962