/* * * Set disk quota from command line * * Jan Kara - sponsored by SuSE CR */ #include "config.h" #if defined(RPC) #include #endif #include #include #include #include #include #include #include #include #if defined(RPC) #include "rquota.h" #include "rquota_client.h" #endif #include "pot.h" #include "quotaops.h" #include "common.h" #include "quotasys.h" #define FL_USER 1 #define FL_GROUP 2 #define FL_RPC 4 #define FL_ALL 8 #define FL_PROTO 16 #define FL_GRACE 32 #define FL_INDIVIDUAL_GRACE 64 #define FL_BATCH 128 #define FL_NUMNAMES 256 #define FL_NO_MIXED_PATHS 512 #define FL_CONTINUE_BATCH 1024 #define FL_PROJECT 2048 static int flags, fmt = -1; static char **mnt; char *progname; static int mntcnt; static qid_t protoid, id; static struct util_dqblk toset; /* Print usage information */ static void usage(void) { #if defined(RPC_SETQUOTA) char *ropt = "[-rm] "; #else char *ropt = ""; #endif errstr(_("Usage:\n\ setquota [-u|-g|-P] %1$s[-F quotaformat] \n\ \t -a|...\n\ setquota [-u|-g|-P] %1$s[-F quotaformat] <-p protouser|protogroup|protoproject> -a|...\n\ setquota [-u|-g|-P] %1$s[-F quotaformat] -b [-c] -a|...\n\ setquota [-u|-g|-P] [-F quotaformat] -t -a|...\n\ setquota [-u|-g|-P] [-F quotaformat] -T -a|...\n\n\ -u, --user set limits for user\n\ -g, --group set limits for group\n\ -P, --project set limits for project\n\ -a, --all set limits for all filesystems\n\ --always-resolve always try to resolve name, even if is\n\ composed only of digits\n\ -F, --format=formatname operate on specific quota format\n\ -p, --prototype=protoname copy limits from user/group/project\n\ -b, --batch read limits from standard input\n\ -c, --continue-batch continue in input processing in case of an error\n"), ropt); #if defined(RPC_SETQUOTA) fputs(_("-r, --remote set remote quota (via RPC)\n\ -m, --no-mixed-pathnames trim leading slashes from NFSv4 mountpoints\n"), stderr); #endif fputs(_("-t, --edit-period edit grace period\n\ -T, --edit-times edit grace times for user/group/project\n\ -h, --help display this help text and exit\n\ -V, --version display version information and exit\n\n"), stderr); fprintf(stderr, _("Bugs to: %s\n"), PACKAGE_BUGREPORT); exit(1); } /* Convert string to number - print errstr message in case of failure */ static qsize_t parse_unum(char *str, char *msg) { char *errch; qsize_t ret = strtoull(str, &errch, 0); if (*errch) { errstr(_("%s: %s\n"), msg, str); usage(); } return ret; } /* Convert block size to number - print errstr message in case of failure */ static qsize_t parse_blocksize(const char *str, const char *msg) { qsize_t ret; const char *error = str2space(str, &ret); if (error) { errstr(_("%s: %s: %s\n"), msg, str, error); usage(); } return ret; } /* Convert inode count to number - print errstr message in case of failure */ static qsize_t parse_inodecount(const char *str, const char *msg) { qsize_t ret; const char *error = str2number(str, &ret); if (error) { errstr(_("%s: %s: %s\n"), msg, str, error); usage(); } return ret; } /* Convert our flags to quota type */ static inline int flag2type(int flags) { if (flags & FL_USER) return USRQUOTA; if (flags & FL_GROUP) return GRPQUOTA; if (flags & FL_PROJECT) return PRJQUOTA; return -1; } /* Parse options of setquota */ static void parse_options(int argcnt, char **argstr) { int ret, otherargs; char *protoname = NULL; int type = 0; #ifdef RPC_SETQUOTA char *opts = "ghp:uPrmVF:taTbc"; #else char *opts = "ghp:uPVF:taTbc"; #endif struct option long_opts[] = { { "user", 0, NULL, 'u' }, { "group", 0, NULL, 'g' }, { "project", 0, NULL, 'P' }, { "prototype", 1, NULL, 'p' }, #ifdef RPC_SETQUOTA { "remote", 0, NULL, 'r' }, { "no-mixed-pathnames", 0, NULL, 'm' }, #endif { "all", 0, NULL, 'a' }, { "always-resolve", 0, NULL, 256}, { "edit-period", 0, NULL, 't' }, { "edit-times", 0, NULL, 'T' }, { "batch", 0, NULL, 'b' }, { "continue", 0, NULL, 'c' }, { "format", 1, NULL, 'F' }, { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'h' }, { NULL, 0, NULL, 0 } }; while ((ret = getopt_long(argcnt, argstr, opts, long_opts, NULL)) != -1) { switch (ret) { case '?': case 'h': usage(); case 'g': flags |= FL_GROUP; break; case 'u': flags |= FL_USER; break; case 'P': flags |= FL_PROJECT; break; case 'p': flags |= FL_PROTO; protoname = optarg; break; case 'r': flags |= FL_RPC; break; case 'm': flags |= FL_NO_MIXED_PATHS; break; case 'a': flags |= FL_ALL; break; case 256: flags |= FL_NUMNAMES; break; case 't': flags |= FL_GRACE; break; case 'b': flags |= FL_BATCH; break; case 'c': flags |= FL_CONTINUE_BATCH; break; case 'T': flags |= FL_INDIVIDUAL_GRACE; break; case 'F': if ((fmt = name2fmt(optarg)) == QF_ERROR) exit(1); break; case 'V': version(); exit(0); } } if (flags & FL_USER) type++; if (flags & FL_GROUP) type++; if (flags & FL_PROJECT) type++; if (type > 1) { errstr(_("Group/user/project quotas cannot be used together.\n")); usage(); } if (flags & FL_PROTO && flags & FL_GRACE) { errstr(_("Prototype user has no sense when editing grace times.\n")); usage(); } if (flags & FL_INDIVIDUAL_GRACE && flags & FL_GRACE) { errstr(_("Cannot set both individual and global grace time.\n")); usage(); } if (flags & FL_BATCH && flags & (FL_GRACE | FL_INDIVIDUAL_GRACE)) { errstr(_("Batch mode cannot be used for setting grace times.\n")); usage(); } if (flags & FL_BATCH && flags & FL_PROTO) { errstr(_("Batch mode and prototype user cannot be used together.\n")); usage(); } if (flags & FL_RPC && (flags & (FL_GRACE | FL_INDIVIDUAL_GRACE))) { errstr(_("Cannot set grace times over RPC protocol.\n")); usage(); } if (flags & FL_GRACE) otherargs = 2; else if (flags & FL_INDIVIDUAL_GRACE) otherargs = 3; else if (flags & FL_BATCH) otherargs = 0; else { otherargs = 1; if (!(flags & FL_PROTO)) otherargs += 4; } if (optind + otherargs > argcnt) { errstr(_("Bad number of arguments.\n")); usage(); } if (!(flags & (FL_USER | FL_GROUP | FL_PROJECT))) flags |= FL_USER; if (!(flags & (FL_GRACE | FL_BATCH))) { id = name2id(argstr[optind++], flag2type(flags), !!(flags & FL_NUMNAMES), NULL); if (!(flags & (FL_GRACE | FL_INDIVIDUAL_GRACE | FL_PROTO))) { toset.dqb_bsoftlimit = parse_blocksize(argstr[optind++], _("Bad block softlimit")); toset.dqb_bhardlimit = parse_blocksize(argstr[optind++], _("Bad block hardlimit")); toset.dqb_isoftlimit = parse_inodecount(argstr[optind++], _("Bad inode softlimit")); toset.dqb_ihardlimit = parse_inodecount(argstr[optind++], _("Bad inode hardlimit")); } else if (flags & FL_PROTO) protoid = name2id(protoname, flag2type(flags), !!(flags & FL_NUMNAMES), NULL); } if (flags & FL_GRACE) { toset.dqb_btime = parse_unum(argstr[optind++], _("Bad block grace time")); toset.dqb_itime = parse_unum(argstr[optind++], _("Bad inode grace time")); } else if (flags & FL_INDIVIDUAL_GRACE) { time_t now; time(&now); if (!strcmp(argstr[optind], _("unset"))) { toset.dqb_btime = 0; optind++; } else toset.dqb_btime = now + parse_unum(argstr[optind++], _("Bad block grace time")); if (!strcmp(argstr[optind], _("unset"))) { toset.dqb_itime = 0; optind++; } else toset.dqb_itime = now + parse_unum(argstr[optind++], _("Bad inode grace time")); } if (!(flags & FL_ALL)) { mntcnt = argcnt - optind; mnt = argstr + optind; if (!mntcnt) { errstr(_("Mountpoint not specified.\n")); usage(); } } } /* Set user limits */ static int setlimits(struct quota_handle **handles) { struct dquot *q, *protoq, *protoprivs = NULL, *curprivs; int ret = 0; curprivs = getprivs(id, handles, !!(flags & FL_ALL)); if (!curprivs) { errstr(_("Error getting quota information to update.\n")); return -1; } if (flags & FL_PROTO) { protoprivs = getprivs(protoid, handles, 0); if (!protoprivs) { errstr(_("Error getting prototype quota information.\n")); ret = -1; goto out; } for (q = curprivs, protoq = protoprivs; q && protoq; q = q->dq_next, protoq = protoq->dq_next) { q->dq_dqb.dqb_bsoftlimit = protoq->dq_dqb.dqb_bsoftlimit; q->dq_dqb.dqb_bhardlimit = protoq->dq_dqb.dqb_bhardlimit; q->dq_dqb.dqb_isoftlimit = protoq->dq_dqb.dqb_isoftlimit; q->dq_dqb.dqb_ihardlimit = protoq->dq_dqb.dqb_ihardlimit; update_grace_times(q); } freeprivs(protoprivs); } else { for (q = curprivs; q; q = q->dq_next) { q->dq_dqb.dqb_bsoftlimit = toset.dqb_bsoftlimit; q->dq_dqb.dqb_bhardlimit = toset.dqb_bhardlimit; q->dq_dqb.dqb_isoftlimit = toset.dqb_isoftlimit; q->dq_dqb.dqb_ihardlimit = toset.dqb_ihardlimit; update_grace_times(q); } } if (putprivs(curprivs, COMMIT_LIMITS) == -1) ret = -1; out: freeprivs(curprivs); return ret; } #define MAXLINELEN 65536 /* Read & parse one batch entry */ static int read_entry(qid_t *id, qsize_t *isoftlimit, qsize_t *ihardlimit, qsize_t *bsoftlimit, qsize_t *bhardlimit) { static int line = 0; char name[MAXNAMELEN+1]; char linebuf[MAXLINELEN], *chptr; char is[MAXNAMELEN+1], ih[MAXNAMELEN+1]; char bs[MAXNAMELEN+1], bh[MAXNAMELEN+1]; const char *error; int ret; while (1) { line++; if (!fgets(linebuf, sizeof(linebuf), stdin)) return -1; if (linebuf[strlen(linebuf)-1] != '\n') die(1, _("Line %d too long.\n"), line); /* Comment? */ if (linebuf[0] == '#') continue; /* Blank line? */ chptr = linebuf; while (isblank(*chptr)) chptr++; if (*chptr == '\n') continue; ret = sscanf(chptr, "%s %s %s %s %s", name, bs, bh, is, ih); if (ret != 5) { errstr(_("Cannot parse input line %d.\n"), line); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } *id = name2id(name, flag2type(flags), !!(flags & FL_NUMNAMES), &ret); if (ret) { errstr(_("Unable to resolve name '%s' on line %d.\n"), name, line); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } error = str2space(bs, bsoftlimit); if (error) { errstr(_("Unable to parse block soft limit '%s' " "on line %d: %s\n"), bs, line, error); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } error = str2space(bh, bhardlimit); if (error) { errstr(_("Unable to parse block hard limit '%s' " "on line %d: %s\n"), bh, line, error); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } error = str2number(is, isoftlimit); if (error) { errstr(_("Unable to parse inode soft limit '%s' " "on line %d: %s\n"), is, line, error); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } error = str2number(ih, ihardlimit); if (error) { errstr(_("Unable to parse inode hard limit '%s' " "on line %d: %s\n"), ih, line, error); if (!(flags & FL_CONTINUE_BATCH)) die(1, _("Exitting.\n")); errstr(_("Skipping line.\n")); continue; } break; } return 0; } /* Set user limits in batch mode */ static int batch_setlimits(struct quota_handle **handles) { struct dquot *curprivs, *q; qsize_t bhardlimit, bsoftlimit, ihardlimit, isoftlimit; qid_t id; int ret = 0; while (!read_entry(&id, &isoftlimit, &ihardlimit, &bsoftlimit, &bhardlimit)) { curprivs = getprivs(id, handles, !!(flags & FL_ALL)); if (!curprivs) { errstr(_("Error getting quota information to update.\n")); return -1; } for (q = curprivs; q; q = q->dq_next) { q->dq_dqb.dqb_bsoftlimit = bsoftlimit; q->dq_dqb.dqb_bhardlimit = bhardlimit; q->dq_dqb.dqb_isoftlimit = isoftlimit; q->dq_dqb.dqb_ihardlimit = ihardlimit; update_grace_times(q); } if (putprivs(curprivs, COMMIT_LIMITS) == -1) ret = -1; freeprivs(curprivs); } return ret; } /* Set grace times */ static int setgraces(struct quota_handle **handles) { int i, ret = 0; for (i = 0; handles[i]; i++) { if (!handles[i]->qh_ops->write_info) { errstr(_("Setting grace period on %s is not supported.\n"), handles[i]->qh_quotadev); ret = -1; continue; } handles[i]->qh_info.dqi_bgrace = toset.dqb_btime; handles[i]->qh_info.dqi_igrace = toset.dqb_itime; mark_quotafile_info_dirty(handles[i]); } return ret; } /* Set grace times for individual user */ static int setindivgraces(struct quota_handle **handles) { int ret = 0; struct dquot *q, *curprivs; curprivs = getprivs(id, handles, !!(flags & FL_ALL)); if (!curprivs) { errstr(_("Error getting quota information to update.\n")); return -1; } for (q = curprivs; q; q = q->dq_next) { if (!toset.dqb_btime || (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) > q->dq_dqb.dqb_bsoftlimit)) q->dq_dqb.dqb_btime = toset.dqb_btime; else errstr(_("Not setting block grace time on %s because softlimit is not exceeded.\n"), q->dq_h->qh_quotadev); if (!toset.dqb_itime || (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes > q->dq_dqb.dqb_isoftlimit)) q->dq_dqb.dqb_itime = toset.dqb_itime; else errstr(_("Not setting inode grace time on %s because softlimit is not exceeded.\n"), q->dq_h->qh_quotadev); } if (putprivs(curprivs, COMMIT_TIMES) == -1) { int type; if (flags & FL_USER) type = USRQUOTA; else if (flags & FL_GROUP) type = GRPQUOTA; else type = PRJQUOTA; errstr(_("cannot write times for %s. Maybe kernel does not support such operation?\n"), _(type2name(type))); ret = -1; } freeprivs(curprivs); return ret; } int main(int argc, char **argv) { struct quota_handle **handles; int ret; gettexton(); progname = basename(argv[0]); parse_options(argc, argv); init_kernel_interface(); if (flags & FL_ALL) handles = create_handle_list(0, NULL, flag2type(flags), fmt, (flags & FL_NO_MIXED_PATHS) ? 0 : IOI_NFS_MIXED_PATHS, (flags & FL_RPC) ? 0 : MS_LOCALONLY); else handles = create_handle_list(mntcnt, mnt, flag2type(flags), fmt, (flags & FL_NO_MIXED_PATHS) ? 0 : IOI_NFS_MIXED_PATHS, (flags & FL_RPC) ? 0 : MS_LOCALONLY); if (flags & FL_GRACE) ret = setgraces(handles); else if (flags & FL_INDIVIDUAL_GRACE) ret = setindivgraces(handles); else if (flags & FL_BATCH) ret = batch_setlimits(handles); else ret = setlimits(handles); if (dispose_handle_list(handles) == -1) ret = -1; return ret ? 1 : 0; }