blob: bfd6682e4eed87c1876d7861a82bd658388430d4 [file] [log] [blame]
/*
*
* Set disk quota from command line
*
* Jan Kara <jack@suse.cz> - sponsored by SuSE CR
*/
#include "config.h"
#if defined(RPC)
#include <rpc/rpc.h>
#endif
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#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] <user|group|project>\n\
\t<block-softlimit> <block-hardlimit> <inode-softlimit> <inode-hardlimit> -a|<filesystem>...\n\
setquota [-u|-g|-P] %1$s[-F quotaformat] <-p protouser|protogroup|protoproject> <user|group|project> -a|<filesystem>...\n\
setquota [-u|-g|-P] %1$s[-F quotaformat] -b [-c] -a|<filesystem>...\n\
setquota [-u|-g|-P] [-F quotaformat] -t <blockgrace> <inodegrace> -a|<filesystem>...\n\
setquota [-u|-g|-P] [-F quotaformat] <user|group|project> -T <blockgrace> <inodegrace> -a|<filesystem>...\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, 0);
if (flags & FL_PROTO) {
protoprivs = getprivs(protoid, handles, 0);
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;
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, 0);
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, 0);
for (q = curprivs; q; q = q->dq_next) {
if (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 (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;
}