/* * Helper functions to handle lists of quota structures */ #include "config.h" #if defined(RPC) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(RPC) #include "rquota.h" #endif #include "mntopt.h" #include "quotaops.h" #include "pot.h" #include "bylabel.h" #include "common.h" #include "quotasys.h" #include "quotaio.h" /* * Set grace time if needed */ void update_grace_times(struct dquot *q) { time_t now; time(&now); if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) > q->dq_dqb.dqb_bsoftlimit) { if (!q->dq_dqb.dqb_btime) q->dq_dqb.dqb_btime = now + q->dq_h->qh_info.dqi_bgrace; } else q->dq_dqb.dqb_btime = 0; if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes > q->dq_dqb.dqb_isoftlimit) { if (!q->dq_dqb.dqb_itime) q->dq_dqb.dqb_itime = now + q->dq_h->qh_info.dqi_igrace; } else q->dq_dqb.dqb_itime = 0; } /* * Collect the requested quota information. */ struct dquot *getprivs(qid_t id, struct quota_handle **handles, int ignore_noquota) { struct dquot *q, *qtail = NULL, *qhead = NULL; int i; char name[MAXNAMELEN]; #if defined(BSD_BEHAVIOUR) int j, ngroups; uid_t euid; gid_t gidset[NGROUPS_MAX], *gidsetp; #endif for (i = 0; handles[i]; i++) { #if defined(BSD_BEHAVIOUR) switch (handles[i]->qh_type) { case USRQUOTA: euid = geteuid(); if (euid != id && euid != 0) { uid2user(id, name); errstr(_("%s (uid %d): Permission denied\n"), name, id); goto out_err; } break; case GRPQUOTA: if (geteuid() == 0) break; ngroups = sysconf(_SC_NGROUPS_MAX); if (ngroups > NGROUPS_MAX) { gidsetp = malloc(ngroups * sizeof(gid_t)); if (!gidsetp) { gid2group(id, name); errstr(_("%s (gid %d): gid set allocation (%d): %s\n"), name, id, ngroups, strerror(errno)); goto out_err; } } else gidsetp = &gidset[0]; ngroups = getgroups(ngroups, gidsetp); if (ngroups < 0) { if (gidsetp != gidset) free(gidsetp); gid2group(id, name); errstr(_("%s (gid %d): error while trying getgroups(): %s\n"), name, id, strerror(errno)); goto out_err; } for (j = 0; j < ngroups; j++) if (id == gidsetp[j]) break; if (gidsetp != gidset) free(gidsetp); if (j >= ngroups) { gid2group(id, name); errstr(_("%s (gid %d): Permission denied\n"), name, id); goto out_err; } break; default: break; } #endif if (!(q = handles[i]->qh_ops->read_dquot(handles[i], id))) { char *estr; /* If rpc.rquotad is not running, filesystem might be just without quotas... */ if (ignore_noquota && (errno == ENOENT || errno == ECONNREFUSED)) continue; if (errno == ECONNREFUSED) { estr = _("Cannot connect to RPC quota service"); } else if (errno == ENOENT) { estr = _("Quota not enabled"); } else { estr = strerror(errno); } id2name(id, handles[i]->qh_type, name); errstr(_("error while getting quota from %s for %s (id %u): %s\n"), handles[i]->qh_quotadev, name, id, estr); #if defined(BSD_BEHAVIOUR) out_err: #endif freeprivs(qhead); return NULL; } if (qhead == NULL) qhead = q; else qtail->dq_next = q; qtail = q; q->dq_next = NULL; /* This should be already set, but just for sure... */ } return qhead; } /* * Store the requested quota information. */ int putprivs(struct dquot *qlist, int flags) { struct dquot *q; int ret = 0; for (q = qlist; q; q = q->dq_next) { if (q->dq_h->qh_ops->commit_dquot(q, flags) == -1) { errstr(_("Cannot write quota for %u on %s: %s\n"), q->dq_id, q->dq_h->qh_quotadev, strerror(errno)); ret = -1; continue; } } return ret; } /* * Take a list of priviledges and get it edited. */ #define MAX_ED_PARS 128 int editprivs(char *tmpfile) { sigset_t omask, nmask; pid_t pid; int stat; sigemptyset(&nmask); sigaddset(&nmask, SIGINT); sigaddset(&nmask, SIGQUIT); sigaddset(&nmask, SIGHUP); sigprocmask(SIG_SETMASK, &nmask, &omask); if ((pid = fork()) < 0) { errstr("Cannot fork(): %s\n", strerror(errno)); return -1; } if (pid == 0) { char *ed, *actp, *nextp; char *edpars[MAX_ED_PARS]; int i; sigprocmask(SIG_SETMASK, &omask, NULL); if (setgid(getgid())) die(1, _("%s failed: %s\n"), "setgid", strerror(errno)); if (setuid(getuid())) die(1, _("%s failed: %s\n"), "setuid", strerror(errno)); if (!(ed = getenv("VISUAL"))) if (!(ed = getenv("EDITOR"))) ed = _PATH_VI; i = 0; ed = actp = sstrdup(ed); while (actp) { nextp = strchr(actp, ' '); if (nextp) { *nextp = 0; nextp++; } edpars[i++] = actp; if (i == MAX_ED_PARS-2) { errstr(_("Too many parameters to editor.\n")); break; } actp = nextp; } edpars[i++] = tmpfile; edpars[i] = NULL; execvp(edpars[0], edpars); die(1, _("Cannot exec %s\n"), ed); } waitpid(pid, &stat, 0); sigprocmask(SIG_SETMASK, &omask, NULL); return 0; } /* * Duplicate a file descriptor, resetting the file offset. * If it's a write descriptor, also truncate the file. */ static FILE *dup_file(int fd, int is_write) { FILE *fp; if (is_write && ftruncate(fd, 0)) die(1, _("Cannot truncate a file: %s\n"), strerror(errno)); if (lseek(fd, 0, SEEK_SET)) die(1, _("Cannot reset a file offset: %s\n"), strerror(errno)); if ((fd = dup(fd)) < 0) die(1, _("Cannot duplicate a file descriptor: %s\n"), strerror(errno)); if (!(fp = fdopen(fd, is_write ? "w" : "r"))) die(1, is_write ? _("Cannot open a stream to write to: %s\n") : _("Cannot open a stream to read from: %s\n"), strerror(errno)); return fp; } /* * Convert a dquot list to an ASCII file. */ int writeprivs(struct dquot *qlist, int outfd, char *name, int quotatype) { struct dquot *q; FILE *fd; fd = dup_file(outfd, 1); fprintf(fd, _("Disk quotas for %s %s (%cid %d):\n"), _(type2name(quotatype)), name, *type2name(quotatype), qlist->dq_id); fprintf(fd, _(" Filesystem blocks soft hard inodes soft hard\n")); for (q = qlist; q; q = q->dq_next) { fprintf(fd, " %-24s %10llu %10llu %10llu %10llu %8llu %8llu\n", q->dq_h->qh_quotadev, (long long)toqb(q->dq_dqb.dqb_curspace), (long long)q->dq_dqb.dqb_bsoftlimit, (long long)q->dq_dqb.dqb_bhardlimit, (long long)q->dq_dqb.dqb_curinodes, (long long)q->dq_dqb.dqb_isoftlimit, (long long)q->dq_dqb.dqb_ihardlimit); } fclose(fd); return 0; } /* Merge changes on one dev to proper structure in the list */ static void merge_limits_to_list(struct dquot *qlist, char *dev, u_int64_t blocks, u_int64_t bsoft, u_int64_t bhard, u_int64_t inodes, u_int64_t isoft, u_int64_t ihard) { struct dquot *q; for (q = qlist; q; q = q->dq_next) { if (!devcmp_handle(dev, q->dq_h)) continue; q->dq_dqb.dqb_bsoftlimit = bsoft; q->dq_dqb.dqb_bhardlimit = bhard; q->dq_dqb.dqb_isoftlimit = isoft; q->dq_dqb.dqb_ihardlimit = ihard; q->dq_flags |= DQ_FOUND; update_grace_times(q); if (blocks != toqb(q->dq_dqb.dqb_curspace)) errstr(_("WARNING - %s: cannot change current block allocation\n"), q->dq_h->qh_quotadev); if (inodes != q->dq_dqb.dqb_curinodes) errstr(_("WARNING - %s: cannot change current inode allocation\n"), q->dq_h->qh_quotadev); } } /* * Merge changes to an ASCII file into a dquot list. */ int readprivs(struct dquot *qlist, int infd) { FILE *fd; int cnt; qsize_t blocks, bsoft, bhard, inodes, isoft, ihard; struct dquot *q; char fsp[BUFSIZ], line[BUFSIZ]; char blocksstring[BUFSIZ], bsoftstring[BUFSIZ], bhardstring[BUFSIZ]; char inodesstring[BUFSIZ], isoftstring[BUFSIZ], ihardstring[BUFSIZ]; const char *error; fd = dup_file(infd, 0); /* * Discard title lines, then read lines to process. */ if (!fgets(line, sizeof(line), fd) || !fgets(line, sizeof(line), fd)) { errstr(_("Bad format: two title lines assumed\n")); fclose(fd); return -1; } while (fgets(line, sizeof(line), fd)) { cnt = sscanf(line, "%s %s %s %s %s %s %s", fsp, blocksstring, bsoftstring, bhardstring, inodesstring, isoftstring, ihardstring); if (cnt != 7) { errstr(_("Bad format:\n%s\n"), line); fclose(fd); return -1; } error = str2space(blocksstring, &blocks); if (error) { errstr(_("Bad block usage: %s: %s\n"), blocksstring, error); fclose(fd); return -1; } error = str2space(bsoftstring, &bsoft); if (error) { errstr(_("Bad block soft limit: %s: %s\n"), bsoftstring, error); fclose(fd); return -1; } error = str2space(bhardstring, &bhard); if (error) { errstr(_("Bad block hard limit: %s: %s\n"), bhardstring, error); fclose(fd); return -1; } error = str2number(inodesstring, &inodes); if (error) { errstr(_("Bad inode usage: %s: %s\n"), inodesstring, error); fclose(fd); return -1; } error = str2number(isoftstring, &isoft); if (error) { errstr(_("Bad inode soft limit: %s: %s\n"), isoftstring, error); fclose(fd); return -1; } error = str2number(ihardstring, &ihard); if (error) { errstr(_("Bad inode hard limit: %s: %s\n"), ihardstring, error); fclose(fd); return -1; } merge_limits_to_list(qlist, fsp, blocks, bsoft, bhard, inodes, isoft, ihard); } fclose(fd); /* * Disable quotas for any filesystems that have not been found. */ for (q = qlist; q; q = q->dq_next) { if (q->dq_flags & DQ_FOUND) { q->dq_flags &= ~DQ_FOUND; continue; } q->dq_dqb.dqb_bsoftlimit = 0; q->dq_dqb.dqb_bhardlimit = 0; q->dq_dqb.dqb_isoftlimit = 0; q->dq_dqb.dqb_ihardlimit = 0; } return 0; } /* Merge changes on one dev to proper structure in the list */ static void merge_times_to_list(struct dquot *qlist, char *dev, time_t btime, time_t itime) { struct dquot *q; for (q = qlist; q; q = q->dq_next) { if (!devcmp_handle(dev, q->dq_h)) continue; if (!btime || (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) > q->dq_dqb.dqb_bsoftlimit)) q->dq_dqb.dqb_btime = btime; else errstr(_("Not setting block grace time on %s because softlimit is not exceeded.\n"), q->dq_h->qh_quotadev); if (!itime || (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes > q->dq_dqb.dqb_isoftlimit)) q->dq_dqb.dqb_itime = itime; else errstr(_("Not setting inode grace time on %s because softlimit is not exceeded.\n"), q->dq_h->qh_quotadev); q->dq_flags |= DQ_FOUND; } } /* * Write grace times of user to file */ int writeindividualtimes(struct dquot *qlist, int outfd, char *name, int quotatype) { struct dquot *q; FILE *fd; time_t now; char btimestr[MAXTIMELEN], itimestr[MAXTIMELEN]; fd = dup_file(outfd, 1); fprintf(fd, _("Times to enforce softlimit for %s %s (%cid %d):\n"), _(type2name(quotatype)), name, *type2name(quotatype), qlist->dq_id); fprintf(fd, _("Time units may be: days, hours, minutes, or seconds\n")); fprintf(fd, _(" Filesystem block grace inode grace\n")); time(&now); for (q = qlist; q; q = q->dq_next) { if (!q->dq_dqb.dqb_btime) strcpy(btimestr, _("unset")); else if (q->dq_dqb.dqb_btime <= now) strcpy(btimestr, _("0seconds")); else sprintf(btimestr, _("%useconds"), (unsigned)(q->dq_dqb.dqb_btime - now)); if (!q->dq_dqb.dqb_itime) strcpy(itimestr, _("unset")); else if (q->dq_dqb.dqb_itime <= now) strcpy(itimestr, _("0seconds")); else sprintf(itimestr, _("%useconds"), (unsigned)(q->dq_dqb.dqb_itime - now)); fprintf(fd, " %-24s %22s %22s\n", q->dq_h->qh_quotadev, btimestr, itimestr); } fclose(fd); return 0; } /* * Read list of grace times for a user and convert it */ int readindividualtimes(struct dquot *qlist, int infd) { FILE *fd; int cnt, btime, itime; char line[BUFSIZ], fsp[BUFSIZ], btimestr[BUFSIZ], itimestr[BUFSIZ]; char iunits[BUFSIZ], bunits[BUFSIZ]; time_t now, bseconds, iseconds; fd = dup_file(infd, 0); /* * Discard title lines, then read lines to process. */ if (!fgets(line, sizeof(line), fd) || !fgets(line, sizeof(line), fd) || !fgets(line, sizeof(line), fd)) { errstr(_("Bad format: three title lines assumed\n")); fclose(fd); return -1; } time(&now); while (fgets(line, sizeof(line), fd)) { cnt = sscanf(line, "%s %s %s", fsp, btimestr, itimestr); if (cnt != 3) { format_err: errstr(_("bad format:\n%s\n"), line); fclose(fd); return -1; } if (!strcmp(btimestr, _("unset"))) bseconds = 0; else { if (sscanf(btimestr, "%d%s", &btime, bunits) != 2) goto format_err; if (str2timeunits(btime, bunits, &bseconds) < 0) { units_err: errstr(_("Bad time units. Units are 'second', 'minute', 'hour', and 'day'.\n")); fclose(fd); return -1; } bseconds += now; } if (!strcmp(itimestr, _("unset"))) iseconds = 0; else { if (sscanf(itimestr, "%d%s", &itime, iunits) != 2) goto format_err; if (str2timeunits(itime, iunits, &iseconds) < 0) goto units_err; iseconds += now; } merge_times_to_list(qlist, fsp, bseconds, iseconds); } fclose(fd); return 0; } /* * Convert a dquot list to an ASCII file of grace times. */ int writetimes(struct quota_handle **handles, int outfd) { FILE *fd; char itimebuf[MAXTIMELEN], btimebuf[MAXTIMELEN]; int i; if (!handles[0]) return 0; fd = dup_file(outfd, 1); fprintf(fd, _("Grace period before enforcing soft limits for %ss:\n"), _(type2name(handles[0]->qh_type))); fprintf(fd, _("Time units may be: days, hours, minutes, or seconds\n")); fprintf(fd, _(" Filesystem Block grace period Inode grace period\n")); for (i = 0; handles[i]; i++) { time2str(handles[i]->qh_info.dqi_bgrace, btimebuf, 0); time2str(handles[i]->qh_info.dqi_igrace, itimebuf, 0); fprintf(fd, " %-12s %22s %22s\n", handles[i]->qh_quotadev, btimebuf, itimebuf); } fclose(fd); return 0; } /* * Merge changes of grace times in an ASCII file into a dquot list. */ int readtimes(struct quota_handle **handles, int infd) { FILE *fd; int itime, btime, i, cnt; time_t iseconds, bseconds; char fsp[BUFSIZ], bunits[10], iunits[10], line[BUFSIZ]; if (!handles[0]) return 0; fd = dup_file(infd, 0); /* Set all grace times to default values */ for (i = 0; handles[i]; i++) { handles[i]->qh_info.dqi_bgrace = MAX_DQ_TIME; handles[i]->qh_info.dqi_igrace = MAX_IQ_TIME; mark_quotafile_info_dirty(handles[i]); } /* * Discard three title lines, then read lines to process. */ if (!fgets(line, sizeof(line), fd) || !fgets(line, sizeof(line), fd) || !fgets(line, sizeof(line), fd)) { errstr(_("Bad format: three title lines assumed\n")); fclose(fd); return -1; } while (fgets(line, sizeof(line), fd)) { cnt = sscanf(line, "%s %d %s %d %s", fsp, &btime, bunits, &itime, iunits); if (cnt != 5) { errstr(_("bad format:\n%s\n"), line); fclose(fd); return -1; } if (str2timeunits(btime, bunits, &bseconds) < 0 || str2timeunits(itime, iunits, &iseconds) < 0) { errstr(_("Bad time units. Units are 'second', 'minute', 'hour', and 'day'.\n")); fclose(fd); return -1; } for (i = 0; handles[i]; i++) { if (!devcmp_handle(fsp, handles[i])) continue; handles[i]->qh_info.dqi_bgrace = bseconds; handles[i]->qh_info.dqi_igrace = iseconds; mark_quotafile_info_dirty(handles[i]); break; } } fclose(fd); return 0; } /* * Free a list of dquot structures. */ void freeprivs(struct dquot *qlist) { struct dquot *q, *nextq; for (q = qlist; q; q = nextq) { nextq = q->dq_next; free(q); } }