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.2 (Berkeley) 11/22/94"; 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, (long)(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, (long)id * (long)sizeof (struct dqblk), 0); 282 if (write(fd, &qup->dqblk, sizeof (struct dqblk)) != 283 sizeof (struct dqblk)) { 284 fprintf(stderr, "edquota: "); 285 perror(qup->qfname); 286 } 287 close(fd); 288 } 289 } 290 } 291 292 /* 293 * Take a list of priviledges and get it edited. 294 */ 295 editit(tmpfile) 296 char *tmpfile; 297 { 298 long omask; 299 int pid, stat; 300 extern char *getenv(); 301 302 omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); 303 top: 304 if ((pid = fork()) < 0) { 305 extern errno; 306 307 if (errno == EPROCLIM) { 308 fprintf(stderr, "You have too many processes\n"); 309 return(0); 310 } 311 if (errno == EAGAIN) { 312 sleep(1); 313 goto top; 314 } 315 perror("fork"); 316 return (0); 317 } 318 if (pid == 0) { 319 register char *ed; 320 321 sigsetmask(omask); 322 setgid(getgid()); 323 setuid(getuid()); 324 if ((ed = getenv("EDITOR")) == (char *)0) 325 ed = _PATH_VI; 326 execlp(ed, ed, tmpfile, 0); 327 perror(ed); 328 exit(1); 329 } 330 waitpid(pid, &stat, 0); 331 sigsetmask(omask); 332 if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) 333 return (0); 334 return (1); 335 } 336 337 /* 338 * Convert a quotause list to an ASCII file. 339 */ 340 writeprivs(quplist, outfd, name, quotatype) 341 struct quotause *quplist; 342 int outfd; 343 char *name; 344 int quotatype; 345 { 346 register struct quotause *qup; 347 FILE *fd; 348 349 ftruncate(outfd, 0); 350 lseek(outfd, 0, L_SET); 351 if ((fd = fdopen(dup(outfd), "w")) == NULL) { 352 fprintf(stderr, "edquota: "); 353 perror(tmpfil); 354 exit(1); 355 } 356 fprintf(fd, "Quotas for %s %s:\n", qfextension[quotatype], name); 357 for (qup = quplist; qup; qup = qup->next) { 358 fprintf(fd, "%s: %s %d, limits (soft = %d, hard = %d)\n", 359 qup->fsname, "blocks in use:", 360 dbtob(qup->dqblk.dqb_curblocks) / 1024, 361 dbtob(qup->dqblk.dqb_bsoftlimit) / 1024, 362 dbtob(qup->dqblk.dqb_bhardlimit) / 1024); 363 fprintf(fd, "%s %d, limits (soft = %d, hard = %d)\n", 364 "\tinodes in use:", qup->dqblk.dqb_curinodes, 365 qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit); 366 } 367 fclose(fd); 368 return (1); 369 } 370 371 /* 372 * Merge changes to an ASCII file into a quotause list. 373 */ 374 readprivs(quplist, infd) 375 struct quotause *quplist; 376 int infd; 377 { 378 register struct quotause *qup; 379 FILE *fd; 380 int cnt; 381 register char *cp; 382 struct dqblk dqblk; 383 char *fsp, line1[BUFSIZ], line2[BUFSIZ]; 384 385 lseek(infd, 0, L_SET); 386 fd = fdopen(dup(infd), "r"); 387 if (fd == NULL) { 388 fprintf(stderr, "Can't re-read temp file!!\n"); 389 return (0); 390 } 391 /* 392 * Discard title line, then read pairs of lines to process. 393 */ 394 (void) fgets(line1, sizeof (line1), fd); 395 while (fgets(line1, sizeof (line1), fd) != NULL && 396 fgets(line2, sizeof (line2), fd) != NULL) { 397 if ((fsp = strtok(line1, " \t:")) == NULL) { 398 fprintf(stderr, "%s: bad format\n", line1); 399 return (0); 400 } 401 if ((cp = strtok((char *)0, "\n")) == NULL) { 402 fprintf(stderr, "%s: %s: bad format\n", fsp, 403 &fsp[strlen(fsp) + 1]); 404 return (0); 405 } 406 cnt = sscanf(cp, 407 " blocks in use: %d, limits (soft = %d, hard = %d)", 408 &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit, 409 &dqblk.dqb_bhardlimit); 410 if (cnt != 3) { 411 fprintf(stderr, "%s:%s: bad format\n", fsp, cp); 412 return (0); 413 } 414 dqblk.dqb_curblocks = btodb(dqblk.dqb_curblocks * 1024); 415 dqblk.dqb_bsoftlimit = btodb(dqblk.dqb_bsoftlimit * 1024); 416 dqblk.dqb_bhardlimit = btodb(dqblk.dqb_bhardlimit * 1024); 417 if ((cp = strtok(line2, "\n")) == NULL) { 418 fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 419 return (0); 420 } 421 cnt = sscanf(cp, 422 "\tinodes in use: %d, limits (soft = %d, hard = %d)", 423 &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit, 424 &dqblk.dqb_ihardlimit); 425 if (cnt != 3) { 426 fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 427 return (0); 428 } 429 for (qup = quplist; qup; qup = qup->next) { 430 if (strcmp(fsp, qup->fsname)) 431 continue; 432 /* 433 * Cause time limit to be reset when the quota 434 * is next used if previously had no soft limit 435 * or were under it, but now have a soft limit 436 * and are over it. 437 */ 438 if (dqblk.dqb_bsoftlimit && 439 qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit && 440 (qup->dqblk.dqb_bsoftlimit == 0 || 441 qup->dqblk.dqb_curblocks < 442 qup->dqblk.dqb_bsoftlimit)) 443 qup->dqblk.dqb_btime = 0; 444 if (dqblk.dqb_isoftlimit && 445 qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit && 446 (qup->dqblk.dqb_isoftlimit == 0 || 447 qup->dqblk.dqb_curinodes < 448 qup->dqblk.dqb_isoftlimit)) 449 qup->dqblk.dqb_itime = 0; 450 qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit; 451 qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit; 452 qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit; 453 qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit; 454 qup->flags |= FOUND; 455 if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks && 456 dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes) 457 break; 458 fprintf(stderr, 459 "%s: cannot change current allocation\n", fsp); 460 break; 461 } 462 } 463 fclose(fd); 464 /* 465 * Disable quotas for any filesystems that have not been found. 466 */ 467 for (qup = quplist; qup; qup = qup->next) { 468 if (qup->flags & FOUND) { 469 qup->flags &= ~FOUND; 470 continue; 471 } 472 qup->dqblk.dqb_bsoftlimit = 0; 473 qup->dqblk.dqb_bhardlimit = 0; 474 qup->dqblk.dqb_isoftlimit = 0; 475 qup->dqblk.dqb_ihardlimit = 0; 476 } 477 return (1); 478 } 479 480 /* 481 * Convert a quotause list to an ASCII file of grace times. 482 */ 483 writetimes(quplist, outfd, quotatype) 484 struct quotause *quplist; 485 int outfd; 486 int quotatype; 487 { 488 register struct quotause *qup; 489 char *cvtstoa(); 490 FILE *fd; 491 492 ftruncate(outfd, 0); 493 lseek(outfd, 0, L_SET); 494 if ((fd = fdopen(dup(outfd), "w")) == NULL) { 495 fprintf(stderr, "edquota: "); 496 perror(tmpfil); 497 exit(1); 498 } 499 fprintf(fd, "Time units may be: days, hours, minutes, or seconds\n"); 500 fprintf(fd, "Grace period before enforcing soft limits for %ss:\n", 501 qfextension[quotatype]); 502 for (qup = quplist; qup; qup = qup->next) { 503 fprintf(fd, "%s: block grace period: %s, ", 504 qup->fsname, cvtstoa(qup->dqblk.dqb_btime)); 505 fprintf(fd, "file grace period: %s\n", 506 cvtstoa(qup->dqblk.dqb_itime)); 507 } 508 fclose(fd); 509 return (1); 510 } 511 512 /* 513 * Merge changes of grace times in an ASCII file into a quotause list. 514 */ 515 readtimes(quplist, infd) 516 struct quotause *quplist; 517 int infd; 518 { 519 register struct quotause *qup; 520 FILE *fd; 521 int cnt; 522 register char *cp; 523 time_t itime, btime, iseconds, bseconds; 524 char *fsp, bunits[10], iunits[10], line1[BUFSIZ]; 525 526 lseek(infd, 0, L_SET); 527 fd = fdopen(dup(infd), "r"); 528 if (fd == NULL) { 529 fprintf(stderr, "Can't re-read temp file!!\n"); 530 return (0); 531 } 532 /* 533 * Discard two title lines, then read lines to process. 534 */ 535 (void) fgets(line1, sizeof (line1), fd); 536 (void) fgets(line1, sizeof (line1), fd); 537 while (fgets(line1, sizeof (line1), fd) != NULL) { 538 if ((fsp = strtok(line1, " \t:")) == NULL) { 539 fprintf(stderr, "%s: bad format\n", line1); 540 return (0); 541 } 542 if ((cp = strtok((char *)0, "\n")) == NULL) { 543 fprintf(stderr, "%s: %s: bad format\n", fsp, 544 &fsp[strlen(fsp) + 1]); 545 return (0); 546 } 547 cnt = sscanf(cp, 548 " block grace period: %d %s file grace period: %d %s", 549 &btime, bunits, &itime, iunits); 550 if (cnt != 4) { 551 fprintf(stderr, "%s:%s: bad format\n", fsp, cp); 552 return (0); 553 } 554 if (cvtatos(btime, bunits, &bseconds) == 0) 555 return (0); 556 if (cvtatos(itime, iunits, &iseconds) == 0) 557 return (0); 558 for (qup = quplist; qup; qup = qup->next) { 559 if (strcmp(fsp, qup->fsname)) 560 continue; 561 qup->dqblk.dqb_btime = bseconds; 562 qup->dqblk.dqb_itime = iseconds; 563 qup->flags |= FOUND; 564 break; 565 } 566 } 567 fclose(fd); 568 /* 569 * reset default grace periods for any filesystems 570 * that have not been found. 571 */ 572 for (qup = quplist; qup; qup = qup->next) { 573 if (qup->flags & FOUND) { 574 qup->flags &= ~FOUND; 575 continue; 576 } 577 qup->dqblk.dqb_btime = 0; 578 qup->dqblk.dqb_itime = 0; 579 } 580 return (1); 581 } 582 583 /* 584 * Convert seconds to ASCII times. 585 */ 586 char * 587 cvtstoa(time) 588 time_t time; 589 { 590 static char buf[20]; 591 592 if (time % (24 * 60 * 60) == 0) { 593 time /= 24 * 60 * 60; 594 sprintf(buf, "%d day%s", time, time == 1 ? "" : "s"); 595 } else if (time % (60 * 60) == 0) { 596 time /= 60 * 60; 597 sprintf(buf, "%d hour%s", time, time == 1 ? "" : "s"); 598 } else if (time % 60 == 0) { 599 time /= 60; 600 sprintf(buf, "%d minute%s", time, time == 1 ? "" : "s"); 601 } else 602 sprintf(buf, "%d second%s", time, time == 1 ? "" : "s"); 603 return (buf); 604 } 605 606 /* 607 * Convert ASCII input times to seconds. 608 */ 609 cvtatos(time, units, seconds) 610 time_t time; 611 char *units; 612 time_t *seconds; 613 { 614 615 if (bcmp(units, "second", 6) == 0) 616 *seconds = time; 617 else if (bcmp(units, "minute", 6) == 0) 618 *seconds = time * 60; 619 else if (bcmp(units, "hour", 4) == 0) 620 *seconds = time * 60 * 60; 621 else if (bcmp(units, "day", 3) == 0) 622 *seconds = time * 24 * 60 * 60; 623 else { 624 printf("%s: bad units, specify %s\n", units, 625 "days, hours, minutes, or seconds"); 626 return (0); 627 } 628 return (1); 629 } 630 631 /* 632 * Free a list of quotause structures. 633 */ 634 freeprivs(quplist) 635 struct quotause *quplist; 636 { 637 register struct quotause *qup, *nextqup; 638 639 for (qup = quplist; qup; qup = nextqup) { 640 nextqup = qup->next; 641 free(qup); 642 } 643 } 644 645 /* 646 * Check whether a string is completely composed of digits. 647 */ 648 alldigits(s) 649 register char *s; 650 { 651 register c; 652 653 c = *s++; 654 do { 655 if (!isdigit(c)) 656 return (0); 657 } while (c = *s++); 658 return (1); 659 } 660 661 /* 662 * Check to see if a particular quota is to be enabled. 663 */ 664 hasquota(fs, type, qfnamep) 665 register struct fstab *fs; 666 int type; 667 char **qfnamep; 668 { 669 register char *opt; 670 char *cp, *index(), *strtok(); 671 static char initname, usrname[100], grpname[100]; 672 static char buf[BUFSIZ]; 673 674 if (!initname) { 675 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname); 676 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname); 677 initname = 1; 678 } 679 strcpy(buf, fs->fs_mntops); 680 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 681 if (cp = index(opt, '=')) 682 *cp++ = '\0'; 683 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 684 break; 685 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 686 break; 687 } 688 if (!opt) 689 return (0); 690 if (cp) { 691 *qfnamep = cp; 692 return (1); 693 } 694 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 695 *qfnamep = buf; 696 return (1); 697 } 698