/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Disk quota editor. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFEDITOR "/usr/bin/vi" #if DEV_BSIZE < 1024 #define dbtok(x) ((x) / (1024 / DEV_BSIZE)) #define ktodb(x) ((x) * (1024 / DEV_BSIZE)) #else #define dbtok(x) ((x) * (DEV_BSIZE / 1024)) #define ktodb(x) ((x) / (DEV_BSIZE / 1024)) #endif struct fsquot { struct fsquot *fsq_next; struct dqblk fsq_dqb; char *fsq_fs; char *fsq_dev; char *fsq_qfile; }; static struct fsquot *fsqlist; static char tmpfil[] = "/tmp/EdP.aXXXXXX"; #define QFNAME "quotas" static uid_t getentry(char *); static int editit(void); static void getprivs(uid_t); static void putprivs(uid_t); static void gettimes(uid_t); static void puttimes(uid_t); static char *next(char *, char *); static int alldigits(char *); static void fmttime(char *, ulong_t); static int unfmttime(double, char *, uint32_t *); static void setupfs(void); static void getdiscq(uid_t); static void putdiscq(uid_t); static void sigsetmask(uint_t); static uint_t sigblock(uint_t); static void usage(void); static int quotactl(int, char *, uid_t, caddr_t); int main(int argc, char **argv) { uid_t uid; char *basename; int opt; int i; int tmpfd = -1; basename = argv[0]; if (argc < 2) { usage(); } if (quotactl(Q_SYNC, (char *)NULL, 0, (caddr_t)NULL) < 0 && errno == EINVAL) { (void) printf("Warning: " "Quotas are not compiled into this kernel\n"); (void) sleep(3); } if (getuid()) { (void) fprintf(stderr, "%s: permission denied\n", basename); exit(32); } setupfs(); if (fsqlist == NULL) { (void) fprintf(stderr, "%s: no UFS filesystems with %s file\n", MNTTAB, QFNAME); exit(32); } tmpfd = mkstemp(tmpfil); if (tmpfd == -1 || fchown(tmpfd, getuid(), getgid()) == -1) { fprintf(stderr, "failure in temporary file %s\n", tmpfil); exit(32); } (void) close(tmpfd); while ((opt = getopt(argc, argv, "p:tV")) != EOF) switch (opt) { case 't': gettimes(0); if (editit()) puttimes(0); (void) unlink(tmpfil); exit(0); /*NOTREACHED*/ case 'p': uid = getentry(optarg); if (uid > MAXUID) { (void) unlink(tmpfil); exit(32); } getprivs(uid); if (optind == argc) { (void) unlink(tmpfil); usage(); } for (i = optind; i < argc; i++) { uid = getentry(argv[i]); if (uid > MAXUID) { (void) unlink(tmpfil); exit(32); } getdiscq(uid); putprivs(uid); } (void) unlink(tmpfil); exit(0); /*NOTREACHED*/ case 'V': /* Print command line */ { char *optt; int optc; (void) printf("edquota -F UFS"); for (optc = 1; optc < argc; optc++) { optt = argv[optc]; if (optt) (void) printf(" %s ", optt); } (void) putchar('\n'); } break; case '?': usage(); } for (i = optind; i < argc; i++) { uid = getentry(argv[i]); if (uid > MAXUID) continue; getprivs(uid); if (editit()) putprivs(uid); if (uid == 0) { (void) printf("edquota: Note that uid 0's quotas " "are used as default values for other users,\n"); (void) printf("not as a limit on the uid 0 user.\n"); } } (void) unlink(tmpfil); return (0); } static uid_t getentry(char *name) { struct passwd *pw; uid_t uid; if (alldigits(name)) { errno = 0; uid = strtol(name, NULL, 10); if (errno == ERANGE) { /* name would cause overflow in uid */ (void) fprintf(stderr, "edquota: uid %s too large\n", name); (void) sleep(1); return (-1); } } else if (pw = getpwnam(name)) uid = pw->pw_uid; else { (void) fprintf(stderr, "%s: no such user\n", name); (void) sleep(1); return (-1); } return (uid); } #define RESPSZ 128 static int editit(void) { pid_t pid, xpid; char *ed; char resp[RESPSZ]; int status, omask; #define mask(s) (1 << ((s) - 1)) omask = sigblock(mask(SIGINT)|mask(SIGQUIT)|mask(SIGHUP)); if ((ed = getenv("EDITOR")) == (char *)0) ed = DEFEDITOR; /*CONSTANTCONDITION*/ while (1) { if ((pid = fork()) < 0) { if (errno == EAGAIN) { (void) fprintf(stderr, "You have too many processes\n"); return (0); } perror("fork"); return (0); } if (pid == 0) { (void) sigsetmask(omask); (void) setgid(getgid()); (void) setuid(getuid()); (void) execlp(ed, ed, tmpfil, 0); (void) fprintf(stderr, "Can't exec editor \"%s\": ", ed); perror(""); exit(32); } while ((xpid = wait(&status)) >= 0) if (xpid == pid) break; if (!isatty(fileno(stdin))) { /* Non-interactive */ break; } /* * Certain editors can exit with a non-zero status even * though everything is peachy. Best to ask the user what * they really wants to do. (N.B.: if we're non-interactive * we'll "break" the while loop before we get here.) */ if (WIFEXITED(status) && (WEXITSTATUS(status) != 0)) { (void) printf("Non-zero return from \"%s\", ", ed); (void) printf("updated file may contain errors.\n"); /*CONSTANTCONDITION*/ while (1) { (void) printf("Edit again (e) or quit, " "discarding changes (q)? "); (void) fflush(stdout); if (gets(resp) == NULL) { return (0); } if ((*resp == 'e') || (*resp == 'q')) { break; } } if (*resp == 'e') { continue; } else { /* * Since (*resp == 'q'), then we just * want to break out of here and return * the failure. */ break; } } else { break; /* Successful return from editor */ } } (void) sigsetmask(omask); return (!status); } static void getprivs(uid_t uid) { struct fsquot *fsqp; FILE *fd; getdiscq(uid); if ((fd = fopen64(tmpfil, "w")) == NULL) { (void) fprintf(stderr, "edquota: "); perror(tmpfil); (void) unlink(tmpfil); exit(32); } for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) (void) fprintf(fd, "fs %s blocks (soft = %lu, hard = %lu) " "inodes (soft = %lu, hard = %lu)\n", fsqp->fsq_fs, dbtok(fsqp->fsq_dqb.dqb_bsoftlimit), dbtok(fsqp->fsq_dqb.dqb_bhardlimit), fsqp->fsq_dqb.dqb_fsoftlimit, fsqp->fsq_dqb.dqb_fhardlimit); (void) fclose(fd); } static void putprivs(uid_t uid) { FILE *fd; uint64_t tmp_bsoftlimit, tmp_bhardlimit, tmp_fsoftlimit, tmp_fhardlimit; char line[BUFSIZ]; int changed = 0; uint32_t max_limit; int quota_entry_printed; fd = fopen64(tmpfil, "r"); if (fd == NULL) { (void) fprintf(stderr, "Can't re-read temp file!!\n"); return; } while (fgets(line, sizeof (line), fd) != NULL) { struct fsquot *fsqp; char *cp, *dp; int n; cp = next(line, " \t"); if (cp == NULL) break; *cp++ = '\0'; while (*cp && *cp == '\t' && *cp == ' ') cp++; dp = cp, cp = next(cp, " \t"); if (cp == NULL) break; *cp++ = '\0'; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (strcmp(dp, fsqp->fsq_fs) == 0) break; } if (fsqp == NULL) { (void) fprintf(stderr, "%s: unknown file system\n", cp); continue; } while (*cp && *cp == '\t' && *cp == ' ') cp++; /* * At this point, dp points to the mount point of the * file system and cp points to the remainder of the * quota definition string. */ n = sscanf(cp, "blocks (soft = %llu, hard = %llu) " "inodes (soft = %llu, hard = %llu)\n", &tmp_bsoftlimit, &tmp_bhardlimit, &tmp_fsoftlimit, &tmp_fhardlimit); if (n != 4) { (void) fprintf(stderr, "%s: bad format\n", cp); continue; } /* * The values in dqb_bsoftlimit and dqb_bhardlimit * are specified in 1k blocks in the edited quota * file (the one we're reading), but are specified in * disk blocks in the data structure passed to quotactl(). * That means that the maximum allowed value for the * hard and soft block limits in the edited quota file * is the maximum number of disk blocks allowed in a * quota (which is 2^32 - 1, since it's a 32-bit unsigned * quantity), converted to 1k blocks. */ max_limit = dbtok(UINT_MAX); quota_entry_printed = 0; /* only print quota entry once */ if (tmp_bsoftlimit > max_limit) { tmp_bsoftlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: soft limit for blocks exceeds maximum allowed value,\n" " soft limit for blocks set to %lu\n", max_limit); } if (tmp_bhardlimit > max_limit) { tmp_bhardlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: hard limit for blocks exceeds maximum allowed value,\n" " hard limit for blocks set to %lu\n", max_limit); } /* * Now check the file limits against their maximum, which * is UINT_MAX (since it must fit in a uint32_t). */ max_limit = UINT_MAX; if (tmp_fsoftlimit > max_limit) { tmp_fsoftlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: soft limit for files exceeds maximum allowed value,\n" " soft limit for files set to %lu\n", max_limit); } if (tmp_fhardlimit > max_limit) { tmp_fhardlimit = max_limit; if (!quota_entry_printed) { (void) fprintf(stderr, "%s %s%\n", dp, cp); quota_entry_printed = 1; } (void) fprintf(stderr, "error: hard limit for files exceeds maximum allowed value,\n" " hard limit for files set to %lu\n", max_limit); } changed++; tmp_bsoftlimit = ktodb(tmp_bsoftlimit); tmp_bhardlimit = ktodb(tmp_bhardlimit); /* * It we are decreasing the soft limits, set the time limits * to zero, in case the user is now over quota. * the time limit will be started the next time the * user does an allocation. */ if (tmp_bsoftlimit < fsqp->fsq_dqb.dqb_bsoftlimit) fsqp->fsq_dqb.dqb_btimelimit = 0; if (tmp_fsoftlimit < fsqp->fsq_dqb.dqb_fsoftlimit) fsqp->fsq_dqb.dqb_ftimelimit = 0; fsqp->fsq_dqb.dqb_bsoftlimit = tmp_bsoftlimit; fsqp->fsq_dqb.dqb_bhardlimit = tmp_bhardlimit; fsqp->fsq_dqb.dqb_fsoftlimit = tmp_fsoftlimit; fsqp->fsq_dqb.dqb_fhardlimit = tmp_fhardlimit; } (void) fclose(fd); if (changed) putdiscq(uid); } static void gettimes(uid_t uid) { struct fsquot *fsqp; FILE *fd; char btime[80], ftime[80]; getdiscq(uid); if ((fd = fopen64(tmpfil, "w")) == NULL) { (void) fprintf(stderr, "edquota: "); perror(tmpfil); (void) unlink(tmpfil); exit(32); } for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { fmttime(btime, fsqp->fsq_dqb.dqb_btimelimit); fmttime(ftime, fsqp->fsq_dqb.dqb_ftimelimit); (void) fprintf(fd, "fs %s blocks time limit = %s, files time limit = %s\n", fsqp->fsq_fs, btime, ftime); } (void) fclose(fd); } static void puttimes(uid_t uid) { FILE *fd; char line[BUFSIZ]; int changed = 0; double btimelimit, ftimelimit; char bunits[80], funits[80]; fd = fopen64(tmpfil, "r"); if (fd == NULL) { (void) fprintf(stderr, "Can't re-read temp file!!\n"); return; } while (fgets(line, sizeof (line), fd) != NULL) { struct fsquot *fsqp; char *cp, *dp; int n; cp = next(line, " \t"); if (cp == NULL) break; *cp++ = '\0'; while (*cp && *cp == '\t' && *cp == ' ') cp++; dp = cp, cp = next(cp, " \t"); if (cp == NULL) break; *cp++ = '\0'; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (strcmp(dp, fsqp->fsq_fs) == 0) break; } if (fsqp == NULL) { (void) fprintf(stderr, "%s: unknown file system\n", cp); continue; } while (*cp && *cp == '\t' && *cp == ' ') cp++; n = sscanf(cp, "blocks time limit = %lf %[^,], " "files time limit = %lf %s\n", &btimelimit, bunits, &ftimelimit, funits); if (n != 4 || !unfmttime(btimelimit, bunits, &fsqp->fsq_dqb.dqb_btimelimit) || !unfmttime(ftimelimit, funits, &fsqp->fsq_dqb.dqb_ftimelimit)) { (void) fprintf(stderr, "%s: bad format\n", cp); continue; } changed++; } (void) fclose(fd); if (changed) putdiscq(uid); } static char * next(char *cp, char *match) { char *dp; while (cp && *cp) { for (dp = match; dp && *dp; dp++) if (*dp == *cp) return (cp); cp++; } return ((char *)0); } static int alldigits(char *s) { int c = *s++; do { if (!isdigit(c)) return (0); } while ((c = *s++) != '\0'); return (1); } static struct { int c_secs; /* conversion units in secs */ char *c_str; /* unit string */ } cunits [] = { {60*60*24*28, "month"}, {60*60*24*7, "week"}, {60*60*24, "day"}, {60*60, "hour"}, {60, "min"}, {1, "sec"} }; static void fmttime(char *buf, ulong_t time) { double value; int i; if (time == 0) { (void) strcpy(buf, "0 (default)"); return; } for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) if (time >= cunits[i].c_secs) break; value = (double)time / cunits[i].c_secs; (void) sprintf(buf, "%.2f %s%s", value, cunits[i].c_str, value > 1.0 ? "s" : ""); } static int unfmttime(double value, char *units, uint32_t *timep) { int i; if (value == 0.0) { *timep = 0; return (1); } for (i = 0; i < sizeof (cunits) / sizeof (cunits[0]); i++) { if (strncmp(cunits[i].c_str, units, strlen(cunits[i].c_str)) == 0) break; } if (i >= sizeof (cunits) / sizeof (cunits[0])) return (0); *timep = (ulong_t)(value * cunits[i].c_secs); return (1); } static void setupfs(void) { struct mnttab mntp; struct fsquot *fsqp; struct stat64 statb; dev_t fsdev; FILE *mtab; char qfilename[MAXPATHLEN]; if ((mtab = fopen(MNTTAB, "r")) == (FILE *)0) { perror("/etc/mnttab"); exit(31+1); } while (getmntent(mtab, &mntp) == 0) { if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0) continue; if (stat64(mntp.mnt_special, &statb) < 0) continue; if ((statb.st_mode & S_IFMT) != S_IFBLK) continue; fsdev = statb.st_rdev; (void) snprintf(qfilename, sizeof (qfilename), "%s/%s", mntp.mnt_mountp, QFNAME); if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev) continue; fsqp = malloc(sizeof (struct fsquot)); if (fsqp == NULL) { (void) fprintf(stderr, "out of memory\n"); exit(31+1); } fsqp->fsq_next = fsqlist; fsqp->fsq_fs = strdup(mntp.mnt_mountp); fsqp->fsq_dev = strdup(mntp.mnt_special); fsqp->fsq_qfile = strdup(qfilename); if (fsqp->fsq_fs == NULL || fsqp->fsq_dev == NULL || fsqp->fsq_qfile == NULL) { (void) fprintf(stderr, "out of memory\n"); exit(31+1); } fsqlist = fsqp; } (void) fclose(mtab); } static void getdiscq(uid_t uid) { struct fsquot *fsqp; int fd; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (quotactl(Q_GETQUOTA, fsqp->fsq_dev, uid, (caddr_t)&fsqp->fsq_dqb) != 0) { if ((fd = open64(fsqp->fsq_qfile, O_RDONLY)) < 0) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); continue; } (void) llseek(fd, (offset_t)dqoff(uid), L_SET); switch (read(fd, (char *)&fsqp->fsq_dqb, sizeof (struct dqblk))) { case 0: /* * Convert implicit 0 quota (EOF) * into an explicit one (zero'ed dqblk) */ bzero((caddr_t)&fsqp->fsq_dqb, sizeof (struct dqblk)); break; case sizeof (struct dqblk): /* OK */ break; default: /* ERROR */ (void) fprintf(stderr, "edquota: read error in "); perror(fsqp->fsq_qfile); break; } (void) close(fd); } } } static void putdiscq(uid_t uid) { struct fsquot *fsqp; for (fsqp = fsqlist; fsqp; fsqp = fsqp->fsq_next) { if (quotactl(Q_SETQLIM, fsqp->fsq_dev, uid, (caddr_t)&fsqp->fsq_dqb) != 0) { int fd; if ((fd = open64(fsqp->fsq_qfile, O_RDWR)) < 0) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); continue; } (void) llseek(fd, (offset_t)dqoff(uid), L_SET); if (write(fd, (char *)&fsqp->fsq_dqb, sizeof (struct dqblk)) != sizeof (struct dqblk)) { (void) fprintf(stderr, "edquota: "); perror(fsqp->fsq_qfile); } (void) close(fd); } } } static void sigsetmask(uint_t omask) { int i; for (i = 0; i < 32; i++) if (omask & (1 << i)) { if (sigignore(1 << i) == (int)SIG_ERR) { (void) fprintf(stderr, "Bad signal 0x%x\n", (1 << i)); exit(31+1); } } } static uint_t sigblock(uint_t omask) { uint_t previous = 0; uint_t temp; int i; for (i = 0; i < 32; i++) if (omask & (1 << i)) { if ((temp = sigignore(1 << i)) == (int)SIG_ERR) { (void) fprintf(stderr, "Bad signal 0x%x\n", (1 << i)); exit(31+1); } if (i == 0) previous = temp; } return (previous); } static void usage(void) { (void) fprintf(stderr, "ufs usage:\n"); (void) fprintf(stderr, "\tedquota [-p username] username ...\n"); (void) fprintf(stderr, "\tedquota -t\n"); exit(1); } static int quotactl(int cmd, char *special, uid_t uid, caddr_t addr) { int fd; int status; struct quotctl quota; char qfile[MAXPATHLEN]; FILE *fstab; struct mnttab mntp; if ((special == NULL) && (cmd == Q_SYNC)) { cmd = Q_ALLSYNC; /* * need to find an acceptable fd to send this Q_ALLSYNC down * on, it needs to be a ufs fd for vfs to at least call the * real quotactl() in the kernel * Here, try to simply find the starting mountpoint of the * first mounted ufs file system */ } /* * Find the mount point of the special device. This is * because the fcntl that implements the quotactl call has * to go to a real file, and not to the block device. */ if ((fstab = fopen(MNTTAB, "r")) == NULL) { (void) fprintf(stderr, "%s: ", MNTTAB); perror("open"); exit(31+1); } qfile[0] = '\0'; while ((status = getmntent(fstab, &mntp)) == NULL) { /* * check that it is a ufs file system * for all quotactl()s except Q_ALLSYNC check that * the file system is read-write since changes in the * quotas file may be required * for Q_ALLSYNC, this check is skipped since this option * is to determine if quotas are configured into the system */ if (strcmp(mntp.mnt_fstype, MNTTYPE_UFS) != 0 || ((cmd != Q_ALLSYNC) && hasmntopt(&mntp, MNTOPT_RO))) continue; if (cmd == Q_ALLSYNC) { /* implies (special==0) too */ if (strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } break; } if (strcmp(special, mntp.mnt_special) == 0) { if (strlcpy(qfile, mntp.mnt_mountp, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } } } (void) fclose(fstab); if (qfile[0] == '\0') { errno = ENOENT; return (-1); } { int open_flags; if (cmd == Q_ALLSYNC) { open_flags = O_RDONLY; } else { if (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof (qfile)) { errno = ENOENT; return (-1); } open_flags = O_RDWR; } if ((fd = open64(qfile, open_flags)) < 0) { (void) fprintf(stderr, "quotactl: "); perror("open"); exit(31+1); } } quota.op = cmd; quota.uid = uid; quota.addr = addr; status = ioctl(fd, Q_QUOTACTL, "a); (void) close(fd); return (status); }