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