1 /* 2 * Copyright (c) 1980, 1990 Regents of the University of California. 3 * 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 * %sccs.include.redist.c% 9 */ 10 11 #ifndef lint 12 char copyright[] = 13 "@(#) Copyright (c) 1980, 1990 Regents of the University of California.\n\ 14 All rights reserved.\n"; 15 #endif /* not lint */ 16 17 #ifndef lint 18 static char sccsid[] = "@(#)edquota.c 5.15 (Berkeley) 09/27/90"; 19 #endif /* not lint */ 20 21 /* 22 * Disk quota editor. 23 */ 24 #include <sys/param.h> 25 #include <sys/stat.h> 26 #include <sys/file.h> 27 #include <sys/wait.h> 28 #include <ufs/quota.h> 29 #include <errno.h> 30 #include <fstab.h> 31 #include <pwd.h> 32 #include <grp.h> 33 #include <ctype.h> 34 #include <stdio.h> 35 #include <string.h> 36 #include "pathnames.h" 37 38 char *qfname = QUOTAFILENAME; 39 char *qfextension[] = INITQFNAMES; 40 char *quotagroup = QUOTAGROUP; 41 char tmpfil[] = _PATH_TMP; 42 43 struct quotause { 44 struct quotause *next; 45 long flags; 46 struct dqblk dqblk; 47 char fsname[MAXPATHLEN + 1]; 48 char qfname[1]; /* actually longer */ 49 } *getprivs(); 50 #define FOUND 0x01 51 52 main(argc, argv) 53 register char **argv; 54 int argc; 55 { 56 register struct quotause *qup, *protoprivs, *curprivs; 57 extern char *optarg; 58 extern int optind; 59 register long id, protoid; 60 register int quotatype, tmpfd; 61 char *protoname, ch; 62 int tflag = 0, pflag = 0; 63 64 if (argc < 2) 65 usage(); 66 if (getuid()) { 67 fprintf(stderr, "edquota: permission denied\n"); 68 exit(1); 69 } 70 quotatype = USRQUOTA; 71 while ((ch = getopt(argc, argv, "ugtp:")) != EOF) { 72 switch(ch) { 73 case 'p': 74 protoname = optarg; 75 pflag++; 76 break; 77 case 'g': 78 quotatype = GRPQUOTA; 79 break; 80 case 'u': 81 quotatype = USRQUOTA; 82 break; 83 case 't': 84 tflag++; 85 break; 86 default: 87 usage(); 88 } 89 } 90 argc -= optind; 91 argv += optind; 92 if (pflag) { 93 if ((protoid = getentry(protoname, quotatype)) == -1) 94 exit(1); 95 protoprivs = getprivs(protoid, quotatype); 96 for (qup = protoprivs; qup; qup = qup->next) { 97 qup->dqblk.dqb_btime = 0; 98 qup->dqblk.dqb_itime = 0; 99 } 100 while (argc-- > 0) { 101 if ((id = getentry(*argv++, quotatype)) < 0) 102 continue; 103 putprivs(id, quotatype, protoprivs); 104 } 105 exit(0); 106 } 107 tmpfd = mkstemp(tmpfil); 108 fchown(tmpfd, getuid(), getgid()); 109 if (tflag) { 110 protoprivs = getprivs(0, quotatype); 111 if (writetimes(protoprivs, tmpfd, quotatype) == 0) 112 exit(1); 113 if (editit(tmpfil) && readtimes(protoprivs, tmpfd)) 114 putprivs(0, quotatype, protoprivs); 115 freeprivs(protoprivs); 116 exit(0); 117 } 118 for ( ; argc > 0; argc--, argv++) { 119 if ((id = getentry(*argv, quotatype)) == -1) 120 continue; 121 curprivs = getprivs(id, quotatype); 122 if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0) 123 continue; 124 if (editit(tmpfil) && readprivs(curprivs, tmpfd)) 125 putprivs(id, quotatype, curprivs); 126 freeprivs(curprivs); 127 } 128 close(tmpfd); 129 unlink(tmpfil); 130 exit(0); 131 } 132 133 usage() 134 { 135 fprintf(stderr, "%s%s%s%s", 136 "Usage: edquota [-u] [-p username] username ...\n", 137 "\tedquota -g [-p groupname] groupname ...\n", 138 "\tedquota [-u] -t\n", "\tedquota -g -t\n"); 139 exit(1); 140 } 141 142 /* 143 * This routine converts a name for a particular quota type to 144 * an identifier. This routine must agree with the kernel routine 145 * getinoquota as to the interpretation of quota types. 146 */ 147 getentry(name, quotatype) 148 char *name; 149 int quotatype; 150 { 151 struct passwd *pw; 152 struct group *gr; 153 154 if (alldigits(name)) 155 return (atoi(name)); 156 switch(quotatype) { 157 case USRQUOTA: 158 if (pw = getpwnam(name)) 159 return (pw->pw_uid); 160 fprintf(stderr, "%s: no such user\n", name); 161 break; 162 case GRPQUOTA: 163 if (gr = getgrnam(name)) 164 return (gr->gr_gid); 165 fprintf(stderr, "%s: no such group\n", name); 166 break; 167 default: 168 fprintf(stderr, "%d: unknown quota type\n", quotatype); 169 break; 170 } 171 sleep(1); 172 return (-1); 173 } 174 175 /* 176 * Collect the requested quota information. 177 */ 178 struct quotause * 179 getprivs(id, quotatype) 180 register long id; 181 int quotatype; 182 { 183 register struct fstab *fs; 184 register struct quotause *qup, *quptail; 185 struct quotause *quphead; 186 int qcmd, qupsize, fd; 187 char *qfpathname; 188 static int warned = 0; 189 extern int errno; 190 191 setfsent(); 192 quphead = (struct quotause *)0; 193 qcmd = QCMD(Q_GETQUOTA, quotatype); 194 while (fs = getfsent()) { 195 if (strcmp(fs->fs_vfstype, "ufs")) 196 continue; 197 if (!hasquota(fs, quotatype, &qfpathname)) 198 continue; 199 qupsize = sizeof(*qup) + strlen(qfpathname); 200 if ((qup = (struct quotause *)malloc(qupsize)) == NULL) { 201 fprintf(stderr, "edquota: out of memory\n"); 202 exit(2); 203 } 204 if (quotactl(fs->fs_file, qcmd, id, &qup->dqblk) != 0) { 205 if (errno == EOPNOTSUPP && !warned) { 206 warned++; 207 fprintf(stderr, "Warning: %s\n", 208 "Quotas are not compiled into this kernel"); 209 sleep(3); 210 } 211 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 212 fd = open(qfpathname, O_RDWR|O_CREAT, 0640); 213 if (fd < 0 && errno != ENOENT) { 214 perror(qfpathname); 215 free(qup); 216 continue; 217 } 218 fprintf(stderr, "Creating quota file %s\n", 219 qfpathname); 220 sleep(3); 221 (void) fchown(fd, getuid(), 222 getentry(quotagroup, GRPQUOTA)); 223 (void) fchmod(fd, 0640); 224 } 225 lseek(fd, (long)(id * sizeof(struct dqblk)), L_SET); 226 switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) { 227 case 0: /* EOF */ 228 /* 229 * Convert implicit 0 quota (EOF) 230 * into an explicit one (zero'ed dqblk) 231 */ 232 bzero((caddr_t)&qup->dqblk, 233 sizeof(struct dqblk)); 234 break; 235 236 case sizeof(struct dqblk): /* OK */ 237 break; 238 239 default: /* ERROR */ 240 fprintf(stderr, "edquota: read error in "); 241 perror(qfpathname); 242 close(fd); 243 free(qup); 244 continue; 245 } 246 close(fd); 247 } 248 strcpy(qup->qfname, qfpathname); 249 strcpy(qup->fsname, fs->fs_file); 250 if (quphead == NULL) 251 quphead = qup; 252 else 253 quptail->next = qup; 254 quptail = qup; 255 qup->next = 0; 256 } 257 endfsent(); 258 return (quphead); 259 } 260 261 /* 262 * Store the requested quota information. 263 */ 264 putprivs(id, quotatype, quplist) 265 long id; 266 int quotatype; 267 struct quotause *quplist; 268 { 269 register struct quotause *qup; 270 int qcmd, fd; 271 272 qcmd = QCMD(Q_SETQUOTA, quotatype); 273 for (qup = quplist; qup; qup = qup->next) { 274 if (quotactl(qup->fsname, qcmd, id, &qup->dqblk) == 0) 275 continue; 276 if ((fd = open(qup->qfname, O_WRONLY)) < 0) { 277 perror(qup->qfname); 278 } else { 279 lseek(fd, (long)id * (long)sizeof (struct dqblk), 0); 280 if (write(fd, &qup->dqblk, sizeof (struct dqblk)) != 281 sizeof (struct dqblk)) { 282 fprintf(stderr, "edquota: "); 283 perror(qup->qfname); 284 } 285 close(fd); 286 } 287 } 288 } 289 290 /* 291 * Take a list of priviledges and get it edited. 292 */ 293 editit(tmpfile) 294 char *tmpfile; 295 { 296 long omask; 297 int pid, stat; 298 extern char *getenv(); 299 300 omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); 301 top: 302 if ((pid = fork()) < 0) { 303 extern errno; 304 305 if (errno == EPROCLIM) { 306 fprintf(stderr, "You have too many processes\n"); 307 return(0); 308 } 309 if (errno == EAGAIN) { 310 sleep(1); 311 goto top; 312 } 313 perror("fork"); 314 return (0); 315 } 316 if (pid == 0) { 317 register char *ed; 318 319 sigsetmask(omask); 320 setgid(getgid()); 321 setuid(getuid()); 322 if ((ed = getenv("EDITOR")) == (char *)0) 323 ed = _PATH_VI; 324 execlp(ed, ed, tmpfile, 0); 325 perror(ed); 326 exit(1); 327 } 328 waitpid(pid, &stat, 0); 329 sigsetmask(omask); 330 if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) 331 return (0); 332 return (1); 333 } 334 335 /* 336 * Convert a quotause list to an ASCII file. 337 */ 338 writeprivs(quplist, outfd, name, quotatype) 339 struct quotause *quplist; 340 int outfd; 341 char *name; 342 int quotatype; 343 { 344 register struct quotause *qup; 345 FILE *fd; 346 347 ftruncate(outfd, 0); 348 lseek(outfd, 0, L_SET); 349 if ((fd = fdopen(dup(outfd), "w")) == NULL) { 350 fprintf(stderr, "edquota: "); 351 perror(tmpfil); 352 exit(1); 353 } 354 fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name); 355 for (qup = quplist; qup; qup = qup->next) { 356 fprintf(fd, "%s: %s %d, limits (soft = %d, hard = %d)\n", 357 qup->fsname, "blocks in use:", 358 dbtob(qup->dqblk.dqb_curblocks) / 1024, 359 dbtob(qup->dqblk.dqb_bsoftlimit) / 1024, 360 dbtob(qup->dqblk.dqb_bhardlimit) / 1024); 361 fprintf(fd, "%s %d, limits (soft = %d, hard = %d)\n", 362 "\tinodes in use:", qup->dqblk.dqb_curinodes, 363 qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit); 364 } 365 fclose(fd); 366 return (1); 367 } 368 369 /* 370 * Merge changes to an ASCII file into a quotause list. 371 */ 372 readprivs(quplist, infd) 373 struct quotause *quplist; 374 int infd; 375 { 376 register struct quotause *qup; 377 FILE *fd; 378 int cnt; 379 register char *cp; 380 struct dqblk dqblk; 381 char *fsp, line1[BUFSIZ], line2[BUFSIZ]; 382 383 lseek(infd, 0, L_SET); 384 fd = fdopen(dup(infd), "r"); 385 if (fd == NULL) { 386 fprintf(stderr, "Can't re-read temp file!!\n"); 387 return (0); 388 } 389 /* 390 * Discard title line, then read pairs of lines to process. 391 */ 392 (void) fgets(line1, sizeof (line1), fd); 393 while (fgets(line1, sizeof (line1), fd) != NULL && 394 fgets(line2, sizeof (line2), fd) != NULL) { 395 if ((fsp = strtok(line1, " \t:")) == NULL) { 396 fprintf(stderr, "%s: bad format\n", line1); 397 return (0); 398 } 399 if ((cp = strtok((char *)0, "\n")) == NULL) { 400 fprintf(stderr, "%s: %s: bad format\n", fsp, 401 &fsp[strlen(fsp) + 1]); 402 return (0); 403 } 404 cnt = sscanf(cp, 405 " blocks in use: %d, limits (soft = %d, hard = %d)", 406 &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit, 407 &dqblk.dqb_bhardlimit); 408 if (cnt != 3) { 409 fprintf(stderr, "%s:%s: bad format\n", fsp, cp); 410 return (0); 411 } 412 dqblk.dqb_curblocks = btodb(dqblk.dqb_curblocks * 1024); 413 dqblk.dqb_bsoftlimit = btodb(dqblk.dqb_bsoftlimit * 1024); 414 dqblk.dqb_bhardlimit = btodb(dqblk.dqb_bhardlimit * 1024); 415 if ((cp = strtok(line2, "\n")) == NULL) { 416 fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 417 return (0); 418 } 419 cnt = sscanf(cp, 420 "\tinodes in use: %d, limits (soft = %d, hard = %d)", 421 &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit, 422 &dqblk.dqb_ihardlimit); 423 if (cnt != 3) { 424 fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 425 return (0); 426 } 427 for (qup = quplist; qup; qup = qup->next) { 428 if (strcmp(fsp, qup->fsname)) 429 continue; 430 /* 431 * Cause time limit to be reset when the quota 432 * is next used if previously had no soft limit 433 * or were under it, but now have a soft limit 434 * and are over it. 435 */ 436 if (dqblk.dqb_bsoftlimit && 437 qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit && 438 (qup->dqblk.dqb_bsoftlimit == 0 || 439 qup->dqblk.dqb_curblocks < 440 qup->dqblk.dqb_bsoftlimit)) 441 qup->dqblk.dqb_btime = 0; 442 if (dqblk.dqb_isoftlimit && 443 qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit && 444 (qup->dqblk.dqb_isoftlimit == 0 || 445 qup->dqblk.dqb_curinodes < 446 qup->dqblk.dqb_isoftlimit)) 447 qup->dqblk.dqb_itime = 0; 448 qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit; 449 qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit; 450 qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit; 451 qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit; 452 qup->flags |= FOUND; 453 if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks && 454 dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes) 455 break; 456 fprintf(stderr, 457 "%s: cannot change current allocation\n", fsp); 458 break; 459 } 460 } 461 fclose(fd); 462 /* 463 * Disable quotas for any filesystems that have not been found. 464 */ 465 for (qup = quplist; qup; qup = qup->next) { 466 if (qup->flags & FOUND) { 467 qup->flags &= ~FOUND; 468 continue; 469 } 470 qup->dqblk.dqb_bsoftlimit = 0; 471 qup->dqblk.dqb_bhardlimit = 0; 472 qup->dqblk.dqb_isoftlimit = 0; 473 qup->dqblk.dqb_ihardlimit = 0; 474 } 475 return (1); 476 } 477 478 /* 479 * Convert a quotause list to an ASCII file of grace times. 480 */ 481 writetimes(quplist, outfd, quotatype) 482 struct quotause *quplist; 483 int outfd; 484 int quotatype; 485 { 486 register struct quotause *qup; 487 char *cvtstoa(); 488 FILE *fd; 489 490 ftruncate(outfd, 0); 491 lseek(outfd, 0, L_SET); 492 if ((fd = fdopen(dup(outfd), "w")) == NULL) { 493 fprintf(stderr, "edquota: "); 494 perror(tmpfil); 495 exit(1); 496 } 497 fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n"); 498 fprintf(fd, "Grace period before enforcing soft limits for %ss:\n", 499 qfextension[quotatype]); 500 for (qup = quplist; qup; qup = qup->next) { 501 fprintf(fd, "%s: block grace period: %s, ", 502 qup->fsname, cvtstoa(qup->dqblk.dqb_btime)); 503 fprintf(fd, "file grace period: %s\n", 504 cvtstoa(qup->dqblk.dqb_itime)); 505 } 506 fclose(fd); 507 return (1); 508 } 509 510 /* 511 * Merge changes of grace times in an ASCII file into a quotause list. 512 */ 513 readtimes(quplist, infd) 514 struct quotause *quplist; 515 int infd; 516 { 517 register struct quotause *qup; 518 FILE *fd; 519 int cnt; 520 register char *cp; 521 time_t itime, btime, iseconds, bseconds; 522 char *fsp, bunits[10], iunits[10], line1[BUFSIZ]; 523 524 lseek(infd, 0, L_SET); 525 fd = fdopen(dup(infd), "r"); 526 if (fd == NULL) { 527 fprintf(stderr, "Can't re-read temp file!!\n"); 528 return (0); 529 } 530 /* 531 * Discard two title lines, then read lines to process. 532 */ 533 (void) fgets(line1, sizeof (line1), fd); 534 (void) fgets(line1, sizeof (line1), fd); 535 while (fgets(line1, sizeof (line1), fd) != NULL) { 536 if ((fsp = strtok(line1, " \t:")) == NULL) { 537 fprintf(stderr, "%s: bad format\n", line1); 538 return (0); 539 } 540 if ((cp = strtok((char *)0, "\n")) == NULL) { 541 fprintf(stderr, "%s: %s: bad format\n", fsp, 542 &fsp[strlen(fsp) + 1]); 543 return (0); 544 } 545 cnt = sscanf(cp, 546 " block grace period: %d %s file grace period: %d %s", 547 &btime, bunits, &itime, iunits); 548 if (cnt != 4) { 549 fprintf(stderr, "%s:%s: bad format\n", fsp, cp); 550 return (0); 551 } 552 if (cvtatos(btime, bunits, &bseconds) == 0) 553 return (0); 554 if (cvtatos(itime, iunits, &iseconds) == 0) 555 return (0); 556 for (qup = quplist; qup; qup = qup->next) { 557 if (strcmp(fsp, qup->fsname)) 558 continue; 559 qup->dqblk.dqb_btime = bseconds; 560 qup->dqblk.dqb_itime = iseconds; 561 qup->flags |= FOUND; 562 break; 563 } 564 } 565 fclose(fd); 566 /* 567 * reset default grace periods for any filesystems 568 * that have not been found. 569 */ 570 for (qup = quplist; qup; qup = qup->next) { 571 if (qup->flags & FOUND) { 572 qup->flags &= ~FOUND; 573 continue; 574 } 575 qup->dqblk.dqb_btime = 0; 576 qup->dqblk.dqb_itime = 0; 577 } 578 return (1); 579 } 580 581 /* 582 * Convert seconds to ASCII times. 583 */ 584 char * 585 cvtstoa(time) 586 time_t time; 587 { 588 static char buf[20]; 589 590 if (time % (24 * 60 * 60) == 0) { 591 time /= 24 * 60 * 60; 592 sprintf(buf, "%d day%s", time, time == 1 ? "" : "s"); 593 } else if (time % (60 * 60) == 0) { 594 time /= 60 * 60; 595 sprintf(buf, "%d hour%s", time, time == 1 ? "" : "s"); 596 } else if (time % 60 == 0) { 597 time /= 60; 598 sprintf(buf, "%d minute%s", time, time == 1 ? "" : "s"); 599 } else 600 sprintf(buf, "%d second%s", time, time == 1 ? "" : "s"); 601 return (buf); 602 } 603 604 /* 605 * Convert ASCII input times to seconds. 606 */ 607 cvtatos(time, units, seconds) 608 time_t time; 609 char *units; 610 time_t *seconds; 611 { 612 613 if (bcmp(units, "second", 6) == 0) 614 *seconds = time; 615 else if (bcmp(units, "minute", 6) == 0) 616 *seconds = time * 60; 617 else if (bcmp(units, "hour", 4) == 0) 618 *seconds = time * 60 * 60; 619 else if (bcmp(units, "day", 3) == 0) 620 *seconds = time * 24 * 60 * 60; 621 else { 622 printf("%s: bad units, specify %s\n", units, 623 "days, hours, minutes, or seconds"); 624 return (0); 625 } 626 return (1); 627 } 628 629 /* 630 * Free a list of quotause structures. 631 */ 632 freeprivs(quplist) 633 struct quotause *quplist; 634 { 635 register struct quotause *qup, *nextqup; 636 637 for (qup = quplist; qup; qup = nextqup) { 638 nextqup = qup->next; 639 free(qup); 640 } 641 } 642 643 /* 644 * Check whether a string is completely composed of digits. 645 */ 646 alldigits(s) 647 register char *s; 648 { 649 register c; 650 651 c = *s++; 652 do { 653 if (!isdigit(c)) 654 return (0); 655 } while (c = *s++); 656 return (1); 657 } 658 659 /* 660 * Check to see if a particular quota is to be enabled. 661 */ 662 hasquota(fs, type, qfnamep) 663 register struct fstab *fs; 664 int type; 665 char **qfnamep; 666 { 667 register char *opt; 668 char *cp, *index(), *strtok(); 669 static char initname, usrname[100], grpname[100]; 670 static char buf[BUFSIZ]; 671 672 if (!initname) { 673 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname); 674 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname); 675 initname = 1; 676 } 677 strcpy(buf, fs->fs_mntops); 678 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 679 if (cp = index(opt, '=')) 680 *cp++ = '\0'; 681 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 682 break; 683 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 684 break; 685 } 686 if (!opt) 687 return (0); 688 if (cp) { 689 *qfnamep = cp; 690 return (1); 691 } 692 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 693 *qfnamep = buf; 694 return (1); 695 } 696