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 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 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 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 * 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 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 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 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 * 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 * 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 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 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 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 * 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 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 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 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 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 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