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