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