1 /* 2 * Copyright (c) 1980, 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Robert Elz at The University of Melbourne. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 4. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #if 0 34 #ifndef lint 35 static const char copyright[] = 36 "@(#) Copyright (c) 1980, 1990, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif /* not lint */ 39 40 #ifndef lint 41 static char sccsid[] = "@(#)edquota.c 8.1 (Berkeley) 6/6/93"; 42 #endif /* not lint */ 43 #endif 44 45 #include <sys/cdefs.h> 46 __FBSDID("$FreeBSD$"); 47 48 /* 49 * Disk quota editor. 50 */ 51 52 #include <sys/param.h> 53 #include <sys/stat.h> 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 = mkstemp(tmpfil); 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 strncpy(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 setgid(getgid()); 457 setuid(getuid()); 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