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