freebsd-dev/usr.bin/quota/quota.c
Brooks Davis 54404cfb13 In preparation for raising NGROUPS and NGROUPS_MAX, change base
system callers of getgroups(), getgrouplist(), and setgroups() to
allocate buffers dynamically.  Specifically, allocate a buffer of size
sysconf(_SC_NGROUPS_MAX)+1 (+2 in a few cases to allow for overflow).

This (or similar gymnastics) is required for the code to actually follow
the POSIX.1-2008 specification where {NGROUPS_MAX} may differ at runtime
and where getgroups may return {NGROUPS_MAX}+1 results on systems like
FreeBSD which include the primary group.

In id(1), don't pointlessly add the primary group to the list of all
groups, it is always the first result from getgroups().  In principle
the old code was more portable, but this was only done in one of the two
places where getgroups() was called to the overall effect was pointless.

Document the actual POSIX requirements in the getgroups(2) and
setgroups(2) manpages.  We do not yet support a dynamic NGROUPS, but we
may in the future.

MFC after:	2 weeks
2009-06-19 15:58:24 +00:00

771 lines
19 KiB
C

/*
* Copyright (c) 1980, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Robert Elz at The University of Melbourne.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1980, 1990, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif
#ifndef lint
static const char sccsid[] = "from: @(#)quota.c 8.1 (Berkeley) 6/6/93";
#endif /* not lint */
/*
* Disk quota reporting program.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <rpc/rpc.h>
#include <rpc/pmap_prot.h>
#include <rpcsvc/rquota.h>
#include <ufs/ufs/quota.h>
#include <ctype.h>
#include <err.h>
#include <fstab.h>
#include <grp.h>
#include <libutil.h>
#include <netdb.h>
#include <pwd.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
const char *qfname = QUOTAFILENAME;
const char *qfextension[] = INITQFNAMES;
struct quotause {
struct quotause *next;
long flags;
struct dqblk dqblk;
char fsname[MAXPATHLEN + 1];
};
static char *timeprt(time_t seconds);
static struct quotause *getprivs(long id, int quotatype);
static void usage(void);
static int showuid(u_long uid);
static int showgid(u_long gid);
static int showusrname(char *name);
static int showgrpname(char *name);
static int showquotas(int type, u_long id, const char *name);
static void showrawquotas(int type, u_long id, struct quotause *qup);
static void heading(int type, u_long id, const char *name, const char *tag);
static int ufshasquota(struct fstab *fs, int type, char **qfnamep);
static int getufsquota(struct fstab *fs, struct quotause *qup, long id,
int quotatype);
static int getnfsquota(struct statfs *fst, struct quotause *qup, long id,
int quotatype);
static int callaurpc(char *host, int prognum, int versnum, int procnum,
xdrproc_t inproc, char *in, xdrproc_t outproc, char *out);
static int alldigits(char *s);
int hflag;
int lflag;
int rflag;
int qflag;
int vflag;
char *filename = NULL;
int
main(int argc, char *argv[])
{
int ngroups;
long ngroups_max;
gid_t mygid, *gidset;
int i, ch, gflag = 0, uflag = 0, errflag = 0;
while ((ch = getopt(argc, argv, "f:ghlrquv")) != -1) {
switch(ch) {
case 'f':
filename = optarg;
break;
case 'g':
gflag++;
break;
case 'h':
hflag++;
break;
case 'l':
lflag++;
break;
case 'q':
qflag++;
break;
case 'r':
rflag++;
break;
case 'u':
uflag++;
break;
case 'v':
vflag++;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (!uflag && !gflag)
uflag++;
if (argc == 0) {
if (uflag)
errflag += showuid(getuid());
if (gflag) {
mygid = getgid();
ngroups_max = sysconf(_SC_NGROUPS_MAX) + 1;
if ((gidset = malloc(sizeof(gid_t) * ngroups_max))
== NULL)
err(1, "malloc");
ngroups = getgroups(ngroups_max, gidset);
if (ngroups < 0)
err(1, "getgroups");
errflag += showgid(mygid);
for (i = 0; i < ngroups; i++)
if (gidset[i] != mygid)
errflag += showgid(gidset[i]);
free(gidset);
}
return(errflag);
}
if (uflag && gflag)
usage();
if (uflag) {
for (; argc > 0; argc--, argv++) {
if (alldigits(*argv))
errflag += showuid(atoi(*argv));
else
errflag += showusrname(*argv);
}
return(errflag);
}
if (gflag) {
for (; argc > 0; argc--, argv++) {
if (alldigits(*argv))
errflag += showgid(atoi(*argv));
else
errflag += showgrpname(*argv);
}
}
return(errflag);
}
static void
usage(void)
{
fprintf(stderr, "%s\n%s\n%s\n",
"usage: quota [-ghlu] [-f path] [-v | -q | -r]",
" quota [-hlu] [-f path] [-v | -q | -r] user ...",
" quota -g [-hl] [-f path] [-v | -q | -r] group ...");
exit(1);
}
/*
* Print out quotas for a specified user identifier.
*/
static int
showuid(u_long uid)
{
struct passwd *pwd = getpwuid(uid);
const char *name;
if (pwd == NULL)
name = "(no account)";
else
name = pwd->pw_name;
return(showquotas(USRQUOTA, uid, name));
}
/*
* Print out quotas for a specifed user name.
*/
static int
showusrname(char *name)
{
struct passwd *pwd = getpwnam(name);
if (pwd == NULL) {
warnx("%s: unknown user", name);
return(1);
}
return(showquotas(USRQUOTA, pwd->pw_uid, name));
}
/*
* Print out quotas for a specified group identifier.
*/
static int
showgid(u_long gid)
{
struct group *grp = getgrgid(gid);
const char *name;
if (grp == NULL)
name = "(no entry)";
else
name = grp->gr_name;
return(showquotas(GRPQUOTA, gid, name));
}
/*
* Print out quotas for a specifed group name.
*/
static int
showgrpname(char *name)
{
struct group *grp = getgrnam(name);
if (grp == NULL) {
warnx("%s: unknown group", name);
return(1);
}
return(showquotas(GRPQUOTA, grp->gr_gid, name));
}
static void
prthumanval(int len, int64_t bytes)
{
char buf[len + 1];
humanize_number(buf, sizeof(buf), bytes, "", HN_AUTOSCALE,
HN_B | HN_NOSPACE | HN_DECIMAL);
(void)printf(" %*s", len, buf);
}
static int
showquotas(int type, u_long id, const char *name)
{
struct quotause *qup;
struct quotause *quplist;
const char *msgi, *msgb;
const char *nam;
char *bgrace = NULL, *igrace = NULL;
int lines = 0, overquota = 0;
static time_t now;
if (now == 0)
time(&now);
quplist = getprivs(id, type);
for (qup = quplist; qup; qup = qup->next) {
msgi = (char *)0;
if (qup->dqblk.dqb_ihardlimit &&
qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) {
overquota++;
msgi = "File limit reached on";
}
else if (qup->dqblk.dqb_isoftlimit &&
qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) {
overquota++;
if (qup->dqblk.dqb_itime > now)
msgi = "In file grace period on";
else
msgi = "Over file quota on";
}
msgb = (char *)0;
if (qup->dqblk.dqb_bhardlimit &&
qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) {
overquota++;
msgb = "Block limit reached on";
}
else if (qup->dqblk.dqb_bsoftlimit &&
qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) {
overquota++;
if (qup->dqblk.dqb_btime > now)
msgb = "In block grace period on";
else
msgb = "Over block quota on";
}
if (rflag) {
showrawquotas(type, id, qup);
continue;
}
if (!vflag &&
qup->dqblk.dqb_isoftlimit == 0 &&
qup->dqblk.dqb_ihardlimit == 0 &&
qup->dqblk.dqb_bsoftlimit == 0 &&
qup->dqblk.dqb_bhardlimit == 0)
continue;
if (qflag) {
if ((msgi != (char *)0 || msgb != (char *)0) &&
lines++ == 0)
heading(type, id, name, "");
if (msgi != (char *)0)
printf("\t%s %s\n", msgi, qup->fsname);
if (msgb != (char *)0)
printf("\t%s %s\n", msgb, qup->fsname);
continue;
}
if (vflag ||
qup->dqblk.dqb_curblocks ||
qup->dqblk.dqb_curinodes) {
if (lines++ == 0)
heading(type, id, name, "");
nam = qup->fsname;
if (strlen(qup->fsname) > 15) {
printf("%s\n", qup->fsname);
nam = "";
}
printf("%15s", nam);
if (hflag) {
printf(" ");
prthumanval(4, dbtob(qup->dqblk.dqb_curblocks));
printf("%c ", (msgb == (char *)0) ? ' ' : '*');
prthumanval(4, dbtob(qup->dqblk.dqb_bsoftlimit));
printf(" ");
prthumanval(4, dbtob(qup->dqblk.dqb_bhardlimit));
} else {
printf(" %7ju%c %6ju %7ju",
(uintmax_t)(dbtob(qup->dqblk.dqb_curblocks)
/ 1024),
(msgb == NULL) ? ' ' : '*',
(uintmax_t)(dbtob(qup->dqblk.dqb_bsoftlimit)
/ 1024),
(uintmax_t)(dbtob(qup->dqblk.dqb_bhardlimit)
/ 1024));
}
if (msgb != NULL)
bgrace = timeprt(qup->dqblk.dqb_btime);
if (msgi != NULL)
igrace = timeprt(qup->dqblk.dqb_itime);
printf(" %7s %7ju%c %6ju %7ju %7s\n",
(msgb == NULL) ? "" : bgrace,
(uintmax_t)qup->dqblk.dqb_curinodes,
(msgi == NULL) ? ' ' : '*',
(uintmax_t)qup->dqblk.dqb_isoftlimit,
(uintmax_t)qup->dqblk.dqb_ihardlimit,
(msgi == NULL) ? "" : igrace
);
if (msgb != NULL)
free(bgrace);
if (msgi != NULL)
free(igrace);
continue;
}
}
if (!qflag && !rflag && lines == 0)
heading(type, id, name, "none");
return(overquota);
}
static void
showrawquotas(int type, u_long id, struct quotause *qup)
{
time_t tt;
printf("Raw %s quota information for id %lu on %s\n",
type == USRQUOTA ? "user" : "group", id, qup->fsname);
printf("block hard limit: %ju\n", (uintmax_t)qup->dqblk.dqb_bhardlimit);
printf("block soft limit: %ju\n", (uintmax_t)qup->dqblk.dqb_bsoftlimit);
printf("current block count: %ju\n", (uintmax_t)qup->dqblk.dqb_curblocks);
printf("i-node hard limit: %ju\n", (uintmax_t)qup->dqblk.dqb_ihardlimit);
printf("i-node soft limit: %ju\n", (uintmax_t)qup->dqblk.dqb_isoftlimit);
printf("current i-node count: %ju\n", (uintmax_t)qup->dqblk.dqb_curinodes);
printf("block grace time: %jd", (intmax_t)qup->dqblk.dqb_btime);
if (qup->dqblk.dqb_btime != 0) {
tt = qup->dqblk.dqb_btime;
printf(" %s", ctime(&tt));
} else
printf("\n");
printf("i-node grace time: %jd", (intmax_t)qup->dqblk.dqb_itime);
if (qup->dqblk.dqb_itime != 0) {
tt = qup->dqblk.dqb_itime;
printf(" %s", ctime(&tt));
} else
printf("\n");
}
static void
heading(int type, u_long id, const char *name, const char *tag)
{
printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type],
name, *qfextension[type], id, tag);
if (!qflag && tag[0] == '\0') {
printf("%15s %7s %6s %7s %7s %7s %6s %7s %7s\n"
, "Filesystem"
, "usage"
, "quota"
, "limit"
, "grace"
, "files"
, "quota"
, "limit"
, "grace"
);
}
}
/*
* Calculate the grace period and return a printable string for it.
*/
static char *
timeprt(time_t seconds)
{
time_t hours, minutes;
char *buf;
static time_t now;
if (now == 0)
time(&now);
if (now > seconds) {
return strdup("none");
}
seconds -= now;
minutes = (seconds + 30) / 60;
hours = (minutes + 30) / 60;
if (hours >= 36) {
if (asprintf(&buf, "%lddays", ((long)hours + 12) / 24) < 0)
errx(1, "asprintf failed in timeprt(1)");
return (buf);
}
if (minutes >= 60) {
if (asprintf(&buf, "%2ld:%ld", (long)minutes / 60,
(long)minutes % 60) < 0)
errx(1, "asprintf failed in timeprt(2)");
return (buf);
}
if (asprintf(&buf, "%2ld", (long)minutes) < 0)
errx(1, "asprintf failed in timeprt(3)");
return (buf);
}
/*
* Collect the requested quota information.
*/
static struct quotause *
getprivs(long id, int quotatype)
{
struct quotause *qup, *quptail = NULL;
struct fstab *fs;
struct quotause *quphead;
struct statfs *fst;
int nfst, i;
struct statfs sfb;
qup = quphead = (struct quotause *)0;
if (filename != NULL && statfs(filename, &sfb) != 0)
err(1, "cannot statfs %s", filename);
nfst = getmntinfo(&fst, MNT_NOWAIT);
if (nfst == 0)
errx(2, "no filesystems mounted!");
setfsent();
for (i=0; i<nfst; i++) {
if (qup == NULL) {
if ((qup = (struct quotause *)malloc(sizeof *qup))
== NULL)
errx(2, "out of memory");
}
/*
* See if the user requested a specific file system
* or specified a file inside a mounted file system.
*/
if (filename != NULL &&
strcmp(sfb.f_mntonname, fst[i].f_mntonname) != 0)
continue;
if (strcmp(fst[i].f_fstypename, "nfs") == 0) {
if (lflag)
continue;
if (getnfsquota(&fst[i], qup, id, quotatype) == 0)
continue;
} else if (strcmp(fst[i].f_fstypename, "ufs") == 0) {
/*
* XXX
* UFS filesystems must be in /etc/fstab, and must
* indicate that they have quotas on (?!) This is quite
* unlike SunOS where quotas can be enabled/disabled
* on a filesystem independent of /etc/fstab, and it
* will still print quotas for them.
*/
if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL)
continue;
if (getufsquota(fs, qup, id, quotatype) == 0)
continue;
} else
continue;
strcpy(qup->fsname, fst[i].f_mntonname);
if (quphead == NULL)
quphead = qup;
else
quptail->next = qup;
quptail = qup;
quptail->next = 0;
qup = NULL;
}
if (qup)
free(qup);
endfsent();
return (quphead);
}
/*
* Check to see if a particular quota is to be enabled.
*/
static int
ufshasquota(struct fstab *fs, int type, char **qfnamep)
{
char *opt;
char *cp;
struct statfs sfb;
static char initname, usrname[100], grpname[100];
static char buf[BUFSIZ];
if (!initname) {
(void)snprintf(usrname, sizeof(usrname), "%s%s",
qfextension[USRQUOTA], qfname);
(void)snprintf(grpname, sizeof(grpname), "%s%s",
qfextension[GRPQUOTA], qfname);
initname = 1;
}
strcpy(buf, fs->fs_mntops);
for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) {
if ((cp = index(opt, '=')))
*cp++ = '\0';
if (type == USRQUOTA && strcmp(opt, usrname) == 0)
break;
if (type == GRPQUOTA && strcmp(opt, grpname) == 0)
break;
}
if (!opt)
return (0);
if (cp)
*qfnamep = cp;
else {
(void)snprintf(buf, sizeof(buf), "%s/%s.%s", fs->fs_file,
qfname, qfextension[type]);
*qfnamep = buf;
}
if (statfs(fs->fs_file, &sfb) != 0) {
warn("cannot statfs mount point %s", fs->fs_file);
return (0);
}
if (strcmp(fs->fs_file, sfb.f_mntonname)) {
warnx("%s not mounted for %s quotas", fs->fs_file,
type == USRQUOTA ? "user" : "group");
return (0);
}
return (1);
}
static int
getufsquota(struct fstab *fs, struct quotause *qup, long id, int quotatype)
{
char *qfpathname;
int fd, qcmd;
qcmd = QCMD(Q_GETQUOTA, quotatype);
if (!ufshasquota(fs, quotatype, &qfpathname))
return (0);
if (quotactl(fs->fs_file, qcmd, id, (char *)&qup->dqblk) != 0) {
if ((fd = open(qfpathname, O_RDONLY)) < 0) {
warn("%s", qfpathname);
return (0);
}
(void) lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET);
switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) {
case 0: /* EOF */
/*
* Convert implicit 0 quota (EOF)
* into an explicit one (zero'ed dqblk)
*/
bzero((caddr_t)&qup->dqblk, sizeof(struct dqblk));
break;
case sizeof(struct dqblk): /* OK */
break;
default: /* ERROR */
warn("read error: %s", qfpathname);
close(fd);
return (0);
}
close(fd);
}
return (1);
}
static int
getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype)
{
struct getquota_args gq_args;
struct getquota_rslt gq_rslt;
struct dqblk *dqp = &qup->dqblk;
struct timeval tv;
char *cp;
if (fst->f_flags & MNT_LOCAL)
return (0);
/*
* rpc.rquotad does not support group quotas
*/
if (quotatype != USRQUOTA)
return (0);
/*
* must be some form of "hostname:/path"
*/
cp = strchr(fst->f_mntfromname, ':');
if (cp == NULL) {
warnx("cannot find hostname for %s", fst->f_mntfromname);
return (0);
}
*cp = '\0';
if (*(cp+1) != '/') {
*cp = ':';
return (0);
}
/* Avoid attempting the RPC for special amd(8) filesystems. */
if (strncmp(fst->f_mntfromname, "pid", 3) == 0 &&
strchr(fst->f_mntfromname, '@') != NULL) {
*cp = ':';
return (0);
}
gq_args.gqa_pathp = cp + 1;
gq_args.gqa_uid = id;
if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS,
RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_getquota_args, (char *)&gq_args,
(xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt) != 0) {
*cp = ':';
return (0);
}
switch (gq_rslt.status) {
case Q_NOQUOTA:
break;
case Q_EPERM:
warnx("quota permission error, host: %s",
fst->f_mntfromname);
break;
case Q_OK:
gettimeofday(&tv, NULL);
/* blocks*/
dqp->dqb_bhardlimit =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit *
(gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
dqp->dqb_bsoftlimit =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit *
(gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
dqp->dqb_curblocks =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks *
(gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE);
/* inodes */
dqp->dqb_ihardlimit =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit;
dqp->dqb_isoftlimit =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit;
dqp->dqb_curinodes =
gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles;
/* grace times */
dqp->dqb_btime =
tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft;
dqp->dqb_itime =
tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft;
*cp = ':';
return (1);
default:
warnx("bad rpc result, host: %s", fst->f_mntfromname);
break;
}
*cp = ':';
return (0);
}
static int
callaurpc(char *host, int prognum, int versnum, int procnum,
xdrproc_t inproc, char *in, xdrproc_t outproc, char *out)
{
struct sockaddr_in server_addr;
enum clnt_stat clnt_stat;
struct hostent *hp;
struct timeval timeout, tottimeout;
CLIENT *client = NULL;
int sock = RPC_ANYSOCK;
if ((hp = gethostbyname(host)) == NULL)
return ((int) RPC_UNKNOWNHOST);
timeout.tv_usec = 0;
timeout.tv_sec = 6;
bcopy(hp->h_addr, &server_addr.sin_addr,
MIN(hp->h_length,(int)sizeof(server_addr.sin_addr)));
server_addr.sin_family = AF_INET;
server_addr.sin_port = 0;
if ((client = clntudp_create(&server_addr, prognum,
versnum, timeout, &sock)) == NULL)
return ((int) rpc_createerr.cf_stat);
client->cl_auth = authunix_create_default();
tottimeout.tv_sec = 25;
tottimeout.tv_usec = 0;
clnt_stat = clnt_call(client, procnum, inproc, in,
outproc, out, tottimeout);
return ((int) clnt_stat);
}
static int
alldigits(char *s)
{
int c;
c = *s++;
do {
if (!isdigit(c))
return (0);
} while ((c = *s++));
return (1);
}