blob: ea79fc8dae9e8a4123aab9e24a9805d0cf60e8ba [file] [log] [blame]
/*
*
* Utility for reporting quotas
*
* Based on old repquota.
* Jan Kara <jack@suse.cz> - Sponsored by SuSE CZ
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <getopt.h>
#include "pot.h"
#include "common.h"
#include "quotasys.h"
#include "quotaio.h"
#define PRINTNAMELEN 9 /* Number of characters to be reserved for name on screen */
#define MAX_CACHE_DQUOTS 1024 /* Number of dquots in cache */
#define FL_USER 1
#define FL_GROUP 2
#define FL_VERBOSE 4
#define FL_ALL 8 /* Dump quota files on all filesystems */
#define FL_TRUNCNAMES 16 /* Truncate names to fit into the screen */
#define FL_SHORTNUMS 32 /* Try to print space in appropriate units */
#define FL_NONAME 64 /* Don't translate ids to names */
#define FL_NOCACHE 128 /* Don't cache dquots before resolving */
#define FL_NOAUTOFS 256 /* Ignore autofs mountpoints */
#define FL_RAWGRACE 512 /* Print grace times in seconds since epoch */
#define FL_PROJECT 1024
static int flags, fmt = -1, ofmt = QOF_DEFAULT;
static char **mnt;
static int mntcnt;
static int cached_dquots;
static struct dquot dquot_cache[MAX_CACHE_DQUOTS];
char *progname;
static void usage(void)
{
errstr(_("Utility for reporting quotas.\nUsage:\n%s [-vugsi] [-c|C] [-t|n] [-F quotaformat] [-O (default | xml | csv)] (-a | mntpoint)\n\n\
-v, --verbose display also users/groups without any usage\n\
-u, --user display information about users\n\
-g, --group display information about groups\n\
-P, --project display information about projects\n\
-s, --human-readable show numbers in human friendly units (MB, GB, ...)\n\
-t, --truncate-names truncate names to 9 characters\n\
-p, --raw-grace print grace time in seconds since epoch\n\
-n, --no-names do not translate uid/gid to name\n\
-i, --no-autofs avoid autofs mountpoints\n\
-c, --cache translate big number of ids at once\n\
-C, --no-cache translate ids one by one\n\
-F, --format=formatname report information for specific format\n\
-O, --output=format format output as xml or csv\n\
-a, --all report information for all mount points with quotas\n\
-h, --help display this help message and exit\n\
-V, --version display version information and exit\n\n"), progname);
fprintf(stderr, _("Bugs to %s\n"), PACKAGE_BUGREPORT);
exit(1);
}
static void parse_options(int argcnt, char **argstr)
{
int ret;
int cache_specified = 0;
struct option long_opts[] = {
{ "version", 0, NULL, 'V' },
{ "all", 0, NULL, 'a' },
{ "verbose", 0, NULL, 'v' },
{ "user", 0, NULL, 'u' },
{ "group", 0, NULL, 'g' },
{ "project", 0, NULL, 'P' },
{ "help", 0, NULL, 'h' },
{ "truncate-names", 0, NULL, 't' },
{ "raw-grace", 0, NULL, 'p' },
{ "human-readable", 0, NULL, 's' },
{ "no-names", 0, NULL, 'n' },
{ "cache", 0, NULL, 'c' },
{ "no-cache", 0, NULL, 'C' },
{ "no-autofs", 0, NULL, 'i' },
{ "format", 1, NULL, 'F' },
{ "output", 1, NULL, 'O' },
{ NULL, 0, NULL, 0 }
};
while ((ret = getopt_long(argcnt, argstr, "VavugPhtspncCiF:O:", long_opts, NULL)) != -1) {
switch (ret) {
case '?':
case 'h':
usage();
case 'V':
version();
exit(0);
case 'u':
flags |= FL_USER;
break;
case 'g':
flags |= FL_GROUP;
break;
case 'P':
flags |= FL_PROJECT;
break;
case 'v':
flags |= FL_VERBOSE;
break;
case 'a':
flags |= FL_ALL;
break;
case 't':
flags |= FL_TRUNCNAMES;
break;
case 'p':
flags |= FL_RAWGRACE;
break;
case 's':
flags |= FL_SHORTNUMS;
break;
case 'C':
flags |= FL_NOCACHE;
cache_specified = 1;
break;
case 'c':
cache_specified = 1;
break;
case 'i':
flags |= FL_NOAUTOFS;
break;
case 'F':
if ((fmt = name2fmt(optarg)) == QF_ERROR)
exit(1);
break;
case 'O':
if ((ofmt = name2ofmt(optarg)) == QOF_ERROR)
exit(1);
break;
case 'n':
flags |= FL_NONAME;
break;
}
}
if ((flags & FL_ALL && optind != argcnt) || (!(flags & FL_ALL) && optind == argcnt)) {
fputs(_("Bad number of arguments.\n"), stderr);
usage();
}
if (fmt == QF_RPC) {
fputs(_("Repquota cannot report through RPC calls.\n"), stderr);
exit(1);
}
if (flags & FL_NONAME && flags & FL_TRUNCNAMES) {
fputs(_("Specified both -n and -t but only one of them can be used.\n"), stderr);
exit(1);
}
if (!(flags & (FL_USER | FL_GROUP | FL_PROJECT)))
flags |= FL_USER;
if (!(flags & FL_ALL)) {
mnt = argstr + optind;
mntcnt = argcnt - optind;
}
if (!cache_specified && !(flags & FL_NONAME) && passwd_handling() == PASSWD_DB)
flags |= FL_NOCACHE;
}
/* Are we over soft or hard limit? */
static char overlim(qsize_t usage, qsize_t softlim, qsize_t hardlim)
{
if ((usage > softlim && softlim) || (usage > hardlim && hardlim))
return '+';
return '-';
}
/* Are we over soft or hard limit? More descriptive */
static char * overlimd(qsize_t usage, qsize_t softlim, qsize_t hardlim)
{
if (usage > hardlim && hardlim)
return "hard";
else if (usage > softlim && softlim)
return "soft";
else
return "ok";
}
/* Print one quota entry */
static void print(struct dquot *dquot, char *name)
{
char pname[MAXNAMELEN];
char time[MAXTIMELEN];
char numbuf[3][MAXNUMLEN];
struct util_dqblk *entry = &dquot->dq_dqb;
if (!entry->dqb_curspace && !entry->dqb_curinodes && !(flags & FL_VERBOSE))
return;
sstrncpy(pname, name, sizeof(pname));
if (flags & FL_TRUNCNAMES)
pname[PRINTNAMELEN] = 0;
if (entry->dqb_bsoftlimit && toqb(entry->dqb_curspace) >= entry->dqb_bsoftlimit)
if (flags & FL_RAWGRACE)
sprintf(time, "%llu", (unsigned long long)entry->dqb_btime);
else
difftime2str(entry->dqb_btime, time);
else
if (flags & FL_RAWGRACE)
strcpy(time, "0");
else
time[0] = 0;
space2str(toqb(entry->dqb_curspace), numbuf[0], flags & FL_SHORTNUMS);
space2str(entry->dqb_bsoftlimit, numbuf[1], flags & FL_SHORTNUMS);
space2str(entry->dqb_bhardlimit, numbuf[2], flags & FL_SHORTNUMS);
if (ofmt == QOF_DEFAULT) {
printf("%-*s %c%c %7s %7s %7s %6s", PRINTNAMELEN, pname,
overlim(qb2kb(toqb(entry->dqb_curspace)), qb2kb(entry->dqb_bsoftlimit), qb2kb(entry->dqb_bhardlimit)),
overlim(entry->dqb_curinodes, entry->dqb_isoftlimit, entry->dqb_ihardlimit),
numbuf[0], numbuf[1], numbuf[2], time);
} else if (ofmt == QOF_CSV) {
printf("%s,%s,%s,%s,%s,%s,%s", pname,
overlimd(qb2kb(toqb(entry->dqb_curspace)),
qb2kb(entry->dqb_bsoftlimit),
qb2kb(entry->dqb_bhardlimit)),
overlimd(entry->dqb_curinodes,
entry->dqb_isoftlimit,
entry->dqb_ihardlimit),
numbuf[0], numbuf[1], numbuf[2], time);
} else if (ofmt == QOF_XML) {
char *spacehdr;
if (flags & FL_SHORTNUMS)
spacehdr = "space";
else
spacehdr = "block";
printf(" <Quota user='%s'>\n\
<QuotaStatus %s='%s' inode='%s' />\n\
<%sLimits used='%s' soft='%s' hard='%s' grace='%s' />\n",
pname, spacehdr,
overlimd(qb2kb(toqb(entry->dqb_curspace)),
qb2kb(entry->dqb_bsoftlimit),
qb2kb(entry->dqb_bhardlimit)),
overlimd(entry->dqb_curinodes,
entry->dqb_isoftlimit,
entry->dqb_ihardlimit),
spacehdr, numbuf[0], numbuf[1], numbuf[2], time);
}
if (entry->dqb_isoftlimit && entry->dqb_curinodes >= entry->dqb_isoftlimit)
if (flags & FL_RAWGRACE)
sprintf(time, "%llu", (unsigned long long)entry->dqb_itime);
else
difftime2str(entry->dqb_itime, time);
else
if (flags & FL_RAWGRACE)
strcpy(time, "0");
else
time[0] = 0;
number2str(entry->dqb_curinodes, numbuf[0], flags & FL_SHORTNUMS);
number2str(entry->dqb_isoftlimit, numbuf[1], flags & FL_SHORTNUMS);
number2str(entry->dqb_ihardlimit, numbuf[2], flags & FL_SHORTNUMS);
if (ofmt == QOF_DEFAULT)
printf(" %7s %5s %5s %6s\n", numbuf[0], numbuf[1], numbuf[2], time);
else if (ofmt == QOF_CSV)
printf(",%s,%s,%s,%s\n", numbuf[0], numbuf[1], numbuf[2], time);
else if (ofmt == QOF_XML)
printf(" <FileLimits used='%s' soft='%s' hard='%s' grace='%s' />\n </Quota>\n", numbuf[0], numbuf[1], numbuf[2], time);
}
/* Print all dquots in the cache */
static void dump_cached_dquots(int type)
{
int i;
char namebuf[MAXNAMELEN];
if (!cached_dquots)
return;
if (type == USRQUOTA) {
struct passwd *pwent;
setpwent();
while ((pwent = getpwent())) {
for (i = 0; i < cached_dquots && pwent->pw_uid != dquot_cache[i].dq_id; i++);
if (i < cached_dquots && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
print(dquot_cache+i, pwent->pw_name);
dquot_cache[i].dq_flags |= DQ_PRINTED;
}
}
endpwent();
}
else if (type == GRPQUOTA) {
struct group *grent;
setgrent();
while ((grent = getgrent())) {
for (i = 0; i < cached_dquots && grent->gr_gid != dquot_cache[i].dq_id; i++);
if (i < cached_dquots && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
print(dquot_cache+i, grent->gr_name);
dquot_cache[i].dq_flags |= DQ_PRINTED;
}
}
endgrent();
} else {
struct fs_project *prent;
setprent();
while ((prent = getprent())) {
for (i = 0; i < cached_dquots && prent->pr_id != dquot_cache[i].dq_id; i++);
if (i < cached_dquots && !(dquot_cache[i].dq_flags & DQ_PRINTED)) {
print(dquot_cache+i, prent->pr_name);
dquot_cache[i].dq_flags |= DQ_PRINTED;
}
}
endprent();
}
for (i = 0; i < cached_dquots; i++)
if (!(dquot_cache[i].dq_flags & DQ_PRINTED)) {
sprintf(namebuf, "#%u", dquot_cache[i].dq_id);
print(dquot_cache+i, namebuf);
}
cached_dquots = 0;
}
/* Callback routine called by scan_dquots on each dquot */
static int output(struct dquot *dquot, char *name)
{
if (flags & FL_NONAME) { /* We should translate names? */
char namebuf[MAXNAMELEN];
sprintf(namebuf, "#%u", dquot->dq_id);
print(dquot, namebuf);
}
else if (name || flags & FL_NOCACHE) { /* We shouldn't do batched id->name translations? */
char namebuf[MAXNAMELEN];
if (!name) {
id2name(dquot->dq_id, dquot->dq_h->qh_type, namebuf);
name = namebuf;
}
print(dquot, name);
}
else { /* Lets cache the dquot for later printing */
memcpy(dquot_cache+cached_dquots++, dquot, sizeof(struct dquot));
if (cached_dquots >= MAX_CACHE_DQUOTS)
dump_cached_dquots(dquot->dq_h->qh_type);
}
return 0;
}
/* Dump information stored in one quota file */
static void report_it(struct quota_handle *h, int type)
{
char bgbuf[MAXTIMELEN], igbuf[MAXTIMELEN];
char *spacehdr;
char *typestr;
if (type == USRQUOTA)
typestr = _("User");
else if (type == GRPQUOTA)
typestr = _("Group");
else
typestr = _("Project");
if (ofmt == QOF_DEFAULT )
printf(_("*** Report for %s quotas on device %s\n"), _(type2name(type)), h->qh_quotadev);
else if (ofmt == QOF_XML)
printf("<Report type='%s' dev='%s'>\n", type2name(type), h->qh_quotadev);
time2str(h->qh_info.dqi_bgrace, bgbuf, TF_ROUND);
time2str(h->qh_info.dqi_igrace, igbuf, TF_ROUND);
if (ofmt == QOF_DEFAULT) {
if (flags & FL_SHORTNUMS)
spacehdr = _("Space");
else
spacehdr = _("Block");
printf(_("Block grace time: %s; Inode grace time: %s\n"), bgbuf, igbuf);
printf(_(" %s limits File limits\n"), spacehdr);
printf(_("%-9s used soft hard grace used soft hard grace\n"), typestr);
printf("----------------------------------------------------------------------\n");
} else if (ofmt == QOF_XML) {
printf(" <BlockGraceTime>%s</BlockGraceTime>\n <InodeGraceTime>%s</InodeGraceTime>\n", bgbuf, igbuf);
} else if (ofmt == QOF_CSV) {
if (flags & FL_SHORTNUMS)
spacehdr = "Space";
else
spacehdr = "Block";
printf("%s,%sStatus,FileStatus,%sUsed,%sSoftLimit,%sHardLimit,%sGrace,FileUsed,FileSoftLimit,FileHardLimit,FileGrace\n",
typestr,spacehdr, spacehdr, spacehdr, spacehdr, spacehdr);
}
if (h->qh_ops->scan_dquots(h, output) < 0)
return;
dump_cached_dquots(type);
if (ofmt == QOF_DEFAULT) {
putchar('\n');
if (h->qh_ops->report)
h->qh_ops->report(h, flags & FL_VERBOSE);
putchar('\n');
}
if (ofmt == QOF_XML)
printf("</Report>\n");
}
static void report(int type)
{
struct quota_handle **handles;
int i;
if (flags & FL_ALL)
handles = create_handle_list(0, NULL, type, fmt, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0));
else
handles = create_handle_list(mntcnt, mnt, type, fmt, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0));
for (i = 0; handles[i]; i++)
report_it(handles[i], type);
dispose_handle_list(handles);
}
int main(int argc, char **argv)
{
gettexton();
progname = basename(argv[0]);
parse_options(argc, argv);
init_kernel_interface();
if (ofmt == QOF_XML)
printf("<?xml version=\"1.0\"?>\n<repquota>\n");
if (flags & FL_USER)
report(USRQUOTA);
if (flags & FL_GROUP)
report(GRPQUOTA);
if (flags & FL_PROJECT)
report(PRJQUOTA);
if (ofmt == QOF_XML)
printf("</repquota>\n");
return 0;
}