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: * 3. All advertising materials mentioning features or use of this software 17: * must display the following acknowledgement: 18: * This product includes software developed by the University of 19: * California, Berkeley and its contributors. 20: * 4. Neither the name of the University nor the names of its contributors 21: * may be used to endorse or promote products derived from this software 22: * without specific prior written permission. 23: * 24: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34: * SUCH DAMAGE. 35: */ 36: 37: #if !defined(lint) && defined(DOSCCS) 38: static char copyright[] = 39: "@(#) Copyright (c) 1980, 1990, 1993\n\ 40: The Regents of the University of California. All rights reserved.\n"; 41: 42: static char sccsid[] = "@(#)edquota.c 8.1.1 (2.11BSD) 1996/1/21"; 43: #endif /* not lint */ 44: 45: /* 46: * Disk quota editor. 47: */ 48: #include <sys/param.h> 49: #include <sys/stat.h> 50: #include <sys/file.h> 51: #include <sys/wait.h> 52: #include <sys/quota.h> 53: #include <errno.h> 54: #include <fstab.h> 55: #include <pwd.h> 56: #include <ctype.h> 57: #include <stdio.h> 58: #include <string.h> 59: #include <unistd.h> 60: #include <paths.h> 61: 62: char *qfname = "quotas"; 63: char tmpfil[] = "/tmp/EdP.aXXXXXX"; 64: 65: struct quotause { 66: struct quotause *next; 67: short flags; 68: struct dqblk dqblk; 69: char fsname[MAXPATHLEN + 1]; 70: char qfname[1]; /* actually longer */ 71: } *getprivs(); 72: #define FOUND 0x01 73: 74: uid_t getentry(); 75: 76: main(argc, argv) 77: register char **argv; 78: int argc; 79: { 80: register struct quotause *protoprivs, *curprivs; 81: extern char *optarg; 82: extern int optind; 83: uid_t id, protoid; 84: int tmpfd; 85: char *protoname, ch; 86: int pflag = 0; 87: 88: if (argc < 2) 89: usage(); 90: if (getuid()) { 91: fprintf(stderr, "edquota: permission denied\n"); 92: exit(1); 93: } 94: while ((ch = getopt(argc, argv, "p:")) != EOF) { 95: switch(ch) { 96: case 'p': 97: protoname = optarg; 98: pflag++; 99: break; 100: default: 101: usage(); 102: } 103: } 104: argc -= optind; 105: argv += optind; 106: if (pflag) { 107: if ((protoid = getentry(protoname)) == -1) 108: exit(1); 109: protoprivs = getprivs(protoid); 110: while (argc-- > 0) { 111: if ((id = getentry(*argv++)) < 0) 112: continue; 113: putprivs(id, protoprivs); 114: } 115: exit(0); 116: } 117: tmpfd = mkstemp(tmpfil); 118: fchown(tmpfd, getuid(), getgid()); 119: for ( ; argc > 0; argc--, argv++) { 120: if ((id = getentry(*argv)) == -1) 121: continue; 122: curprivs = getprivs(id); 123: if (writeprivs(curprivs, tmpfd, *argv) == 0) 124: continue; 125: if (editit(tmpfil) && readprivs(curprivs, tmpfd)) 126: putprivs(id, curprivs); 127: freeprivs(curprivs); 128: } 129: close(tmpfd); 130: unlink(tmpfil); 131: exit(0); 132: } 133: 134: usage() 135: { 136: fputs("Usage: edquota [-p username] username ...\n", stderr); 137: exit(1); 138: } 139: 140: /* 141: * This routine converts a name for a particular quota type to 142: * an identifier. This routine must agree with the kernel routine 143: * getinoquota as to the interpretation of quota types. 144: */ 145: uid_t 146: getentry(name) 147: char *name; 148: { 149: struct passwd *pw; 150: 151: if (alldigits(name)) 152: return (atoi(name)); 153: if (pw = getpwnam(name)) 154: return (pw->pw_uid); 155: fprintf(stderr, "%s: no such user\n", name); 156: sleep(1); 157: return (-1); 158: } 159: 160: /* 161: * Collect the requested quota information. 162: */ 163: struct quotause * 164: getprivs(id) 165: u_int id; 166: { 167: register struct fstab *fs; 168: register struct quotause *qup, *quptail; 169: struct dqblk *dq; 170: struct quotause *quphead; 171: struct stat statb; 172: dev_t fsdev; 173: int qupsize, fd; 174: char *qfpathname; 175: static int warned = 0; 176: extern int errno; 177: 178: setfsent(); 179: quphead = (struct quotause *)0; 180: while (fs = getfsent()) { 181: if (stat(fs->fs_spec, &statb) < 0) 182: continue; 183: if (strcmp(fs->fs_vfstype, "ufs")) 184: continue; 185: if (!hasquota(fs, &qfpathname)) 186: continue; 187: /* 188: * See the comments in quota.c about the check below. 189: */ 190: fsdev = statb.st_rdev; 191: if (stat(qfpathname, &statb) < 0 || statb.st_dev != fsdev) 192: continue; 193: qupsize = sizeof(*qup) + strlen(qfpathname); 194: if ((qup = (struct quotause *)malloc(qupsize)) == NULL) { 195: fprintf(stderr, "edquota: out of memory\n"); 196: exit(2); 197: } 198: if (quota(Q_GETDLIM, id, fsdev, &qup->dqblk) != 0) { 199: if (errno == EOPNOTSUPP && !warned) { 200: warned++; 201: fprintf(stderr, "Warning: %s\n", 202: "Quotas are not compiled into this kernel"); 203: sleep(3); 204: } 205: if ((fd = open(qfpathname, O_RDONLY)) < 0) { 206: fd = open(qfpathname, O_RDWR|O_CREAT, 0640); 207: if (fd < 0 && errno != ENOENT) { 208: perror(qfpathname); 209: free(qup); 210: continue; 211: } 212: fprintf(stderr, "Creating quota file %s\n", 213: qfpathname); 214: sleep(3); 215: (void) fchown(fd, getuid(), -1); 216: (void) fchmod(fd, 0640); 217: } 218: lseek(fd, (long)(id * sizeof(struct dqblk)), L_SET); 219: switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) { 220: case 0: /* EOF */ 221: /* 222: * Convert implicit 0 quota (EOF) 223: * into an explicit one (zero'ed dqblk) 224: */ 225: bzero((caddr_t)&qup->dqblk, 226: sizeof(struct dqblk)); 227: break; 228: 229: case sizeof(struct dqblk): /* OK */ 230: /* 231: * We have to convert from bytes to disc blocks because the quotas file 232: * entries use bytes like the kernel does. 233: */ 234: dq = &qup->dqblk; 235: dq->dqb_curblocks = btodb(dq->dqb_curblocks); 236: dq->dqb_bhardlimit = btodb(dq->dqb_bhardlimit); 237: dq->dqb_bsoftlimit = btodb(dq->dqb_bsoftlimit); 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, quplist) 266: uid_t id; 267: struct quotause *quplist; 268: { 269: register struct quotause *qup; 270: struct stat sb; 271: int fd; 272: register struct dqblk *dq; 273: 274: for (qup = quplist; qup; qup = qup->next) { 275: if (stat(qup->qfname, &sb) < 0) { 276: fprintf(stderr,"edquota: can't stat %s\n",qup->qfname); 277: continue; 278: } 279: 280: if (quota(Q_SETDLIM, id, sb.st_dev, &qup->dqblk) == 0) 281: continue; 282: /* 283: * Have to convert from blocks to bytes now to be compatible with the kernel 284: */ 285: dq = &qup->dqblk; 286: dq->dqb_bhardlimit = dbtob(dq->dqb_bhardlimit); 287: dq->dqb_bsoftlimit = dbtob(dq->dqb_bsoftlimit); 288: dq->dqb_curblocks = dbtob(dq->dqb_curblocks); 289: 290: if ((fd = open(qup->qfname, O_WRONLY)) < 0) { 291: perror(qup->qfname); 292: } else { 293: lseek(fd, (long)id * (long)sizeof (struct dqblk), 0); 294: if (write(fd, &qup->dqblk, sizeof (struct dqblk)) != 295: sizeof (struct dqblk)) { 296: fprintf(stderr, "edquota: "); 297: perror(qup->qfname); 298: } 299: close(fd); 300: } 301: } 302: } 303: 304: /* 305: * Take a list of priviledges and get it edited. 306: */ 307: editit(tmpfile) 308: char *tmpfile; 309: { 310: long omask; 311: pid_t pid; 312: union wait stat; 313: extern char *getenv(); 314: 315: omask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP)); 316: top: 317: if ((pid = vfork()) < 0) { 318: extern int errno; 319: 320: if (errno == EPROCLIM) { 321: fprintf(stderr, "You have too many processes\n"); 322: return(0); 323: } 324: if (errno == EAGAIN) { 325: sleep(1); 326: goto top; 327: } 328: perror("fork"); 329: return (0); 330: } 331: if (pid == 0) { 332: register char *ed; 333: 334: sigsetmask(omask); 335: setgid(getgid()); 336: setuid(getuid()); 337: if ((ed = getenv("EDITOR")) == (char *)0) 338: ed = _PATH_VI; 339: execlp(ed, ed, tmpfile, 0); 340: perror(ed); 341: exit(1); 342: } 343: waitpid(pid, &stat, 0); 344: sigsetmask(omask); 345: if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) 346: return (0); 347: return (1); 348: } 349: 350: /* 351: * Convert a quotause list to an ASCII file. 352: */ 353: writeprivs(quplist, outfd, name) 354: struct quotause *quplist; 355: int outfd; 356: char *name; 357: { 358: register struct quotause *qup; 359: register FILE *fd; 360: 361: ftruncate(outfd, (off_t)0); 362: lseek(outfd, (off_t)0, L_SET); 363: if ((fd = fdopen(dup(outfd), "w")) == NULL) { 364: fprintf(stderr, "edquota: "); 365: perror(tmpfil); 366: exit(1); 367: } 368: fprintf(fd, "Quotas for %s:\n", name); 369: for (qup = quplist; qup; qup = qup->next) { 370: fprintf(fd, "%s: %s %ld, limits (soft = %ld, hard = %ld)\n", 371: qup->fsname, "blocks in use:", 372: qup->dqblk.dqb_curblocks, 373: qup->dqblk.dqb_bsoftlimit, 374: qup->dqblk.dqb_bhardlimit); 375: fprintf(fd, "%s %u, limits (soft = %u, hard = %u)\n", 376: "\tinodes in use:", qup->dqblk.dqb_curinodes, 377: qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit); 378: } 379: fclose(fd); 380: return (1); 381: } 382: 383: /* 384: * Merge changes to an ASCII file into a quotause list. 385: */ 386: readprivs(quplist, infd) 387: struct quotause *quplist; 388: int infd; 389: { 390: register struct quotause *qup; 391: FILE *fd; 392: int cnt; 393: register char *cp; 394: struct dqblk dqblk; 395: char *fsp, line1[BUFSIZ], line2[BUFSIZ]; 396: 397: lseek(infd, (off_t)0, L_SET); 398: fd = fdopen(dup(infd), "r"); 399: if (fd == NULL) { 400: fprintf(stderr, "Can't re-read temp file!!\n"); 401: return (0); 402: } 403: /* 404: * Discard title line, then read pairs of lines to process. 405: */ 406: (void) fgets(line1, sizeof (line1), fd); 407: while (fgets(line1, sizeof (line1), fd) != NULL && 408: fgets(line2, sizeof (line2), fd) != NULL) { 409: if ((fsp = strtok(line1, " \t:")) == NULL) { 410: fprintf(stderr, "%s: bad format\n", line1); 411: return (0); 412: } 413: if ((cp = strtok((char *)0, "\n")) == NULL) { 414: fprintf(stderr, "%s: %s: bad format\n", fsp, 415: &fsp[strlen(fsp) + 1]); 416: return (0); 417: } 418: cnt = sscanf(cp, 419: " blocks in use: %ld, limits (soft = %ld, hard = %ld)", 420: &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit, 421: &dqblk.dqb_bhardlimit); 422: if (cnt != 3) { 423: fprintf(stderr, "%s:%s: bad format\n", fsp, cp); 424: return (0); 425: } 426: if ((cp = strtok(line2, "\n")) == NULL) { 427: fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 428: return (0); 429: } 430: cnt = sscanf(cp, 431: "\tinodes in use: %u, limits (soft = %u, hard = %u)", 432: &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit, 433: &dqblk.dqb_ihardlimit); 434: if (cnt != 3) { 435: fprintf(stderr, "%s: %s: bad format\n", fsp, line2); 436: return (0); 437: } 438: for (qup = quplist; qup; qup = qup->next) { 439: if (strcmp(fsp, qup->fsname)) 440: continue; 441: qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit; 442: qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit; 443: qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit; 444: qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit; 445: qup->flags |= FOUND; 446: if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks && 447: dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes) 448: break; 449: fprintf(stderr, 450: "%s: cannot change current allocation\n", fsp); 451: break; 452: } 453: } 454: fclose(fd); 455: /* 456: * Disable quotas for any filesystems that have not been found. 457: */ 458: for (qup = quplist; qup; qup = qup->next) { 459: if (qup->flags & FOUND) { 460: qup->flags &= ~FOUND; 461: continue; 462: } 463: qup->dqblk.dqb_bsoftlimit = 0; 464: qup->dqblk.dqb_bhardlimit = 0; 465: qup->dqblk.dqb_isoftlimit = 0; 466: qup->dqblk.dqb_ihardlimit = 0; 467: } 468: return (1); 469: } 470: 471: /* 472: * Free a list of quotause structures. 473: */ 474: freeprivs(quplist) 475: struct quotause *quplist; 476: { 477: register struct quotause *qup, *nextqup; 478: 479: for (qup = quplist; qup; qup = nextqup) { 480: nextqup = qup->next; 481: free(qup); 482: } 483: } 484: 485: /* 486: * Check whether a string is completely composed of digits. 487: */ 488: alldigits(s) 489: register char *s; 490: { 491: register c; 492: 493: c = *s++; 494: do { 495: if (!isdigit(c)) 496: return (0); 497: } while (c = *s++); 498: return (1); 499: } 500: 501: /* 502: * Check to see if a particular quota is to be enabled. 503: */ 504: hasquota(fs, qfnamep) 505: register struct fstab *fs; 506: char **qfnamep; 507: { 508: register char *opt; 509: char *cp; 510: static char initname, usrname[100]; 511: static char buf[BUFSIZ]; 512: 513: if (!initname) { 514: strcpy(usrname, qfname); 515: initname = 1; 516: } 517: strcpy(buf, fs->fs_mntops); 518: for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 519: if (cp = index(opt, '=')) 520: *cp++ = '\0'; 521: if (strcmp(opt, usrname) == 0) 522: break; 523: if (strcmp(opt, FSTAB_RQ) == 0) /* XXX compatibility */ 524: break; 525: } 526: if (!opt) 527: return (0); 528: if (cp) { 529: *qfnamep = cp; 530: return (1); 531: } 532: (void) sprintf(buf, "%s/%s", fs->fs_file, qfname); 533: *qfnamep = buf; 534: return (1); 535: }