quota.c revision 101544
1/* 2 * Copyright (c) 1980, 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Robert Elz at The University of Melbourne. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. All advertising materials mentioning features or use of this software 17 * must display the following acknowledgement: 18 * This product includes software developed by the University of 19 * California, Berkeley and its contributors. 20 * 4. Neither the name of the University nor the names of its contributors 21 * may be used to endorse or promote products derived from this software 22 * without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 */ 36 37#ifndef lint 38static const char copyright[] = 39"@(#) Copyright (c) 1980, 1990, 1993\n\ 40 The Regents of the University of California. All rights reserved.\n"; 41#endif 42 43#ifndef lint 44static const char sccsid[] = "from: @(#)quota.c 8.1 (Berkeley) 6/6/93"; 45#endif /* not lint */ 46 47/* 48 * Disk quota reporting program. 49 */ 50#include <sys/cdefs.h> 51__FBSDID("$FreeBSD: head/usr.bin/quota/quota.c 101544 2002-08-09 00:43:36Z iedowse $"); 52 53#include <sys/param.h> 54#include <sys/types.h> 55#include <sys/file.h> 56#include <sys/stat.h> 57#include <sys/mount.h> 58#include <sys/socket.h> 59 60#include <rpc/rpc.h> 61#include <rpc/pmap_prot.h> 62#include <rpcsvc/rquota.h> 63 64#include <ufs/ufs/quota.h> 65 66#include <ctype.h> 67#include <err.h> 68#include <fstab.h> 69#include <grp.h> 70#include <netdb.h> 71#include <pwd.h> 72#include <stdio.h> 73#include <stdlib.h> 74#include <string.h> 75#include <unistd.h> 76 77const char *qfname = QUOTAFILENAME; 78const char *qfextension[] = INITQFNAMES; 79 80struct quotause { 81 struct quotause *next; 82 long flags; 83 struct dqblk dqblk; 84 char fsname[MAXPATHLEN + 1]; 85}; 86#define FOUND 0x01 87 88static const char *timeprt(time_t seconds); 89static struct quotause *getprivs(long id, int quotatype); 90static void usage(void); 91static void showuid(u_long uid); 92static void showgid(u_long gid); 93static int alldigits(char *s); 94static void showusrname(char *name); 95static void showgrpname(char *name); 96static void showquotas(int type, u_long id, const char *name); 97static void heading(int type, u_long id, const char *name, const char *tag); 98static struct quotause *getprivs(long id, int quotatype); 99static int ufshasquota(struct fstab *fs, int type, char **qfnamep); 100static int getufsquota(struct fstab *fs, struct quotause *qup, long id, 101 int quotatype); 102static int getnfsquota(struct statfs *fst, struct quotause *qup, long id, 103 int quotatype); 104static int callaurpc(char *host, int prognum, int versnum, int procnum, 105 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out); 106static int alldigits(char *s); 107 108int qflag; 109int vflag; 110 111int 112main(int argc, char *argv[]) 113{ 114 int ngroups; 115 gid_t mygid, gidset[NGROUPS]; 116 int i, gflag = 0, uflag = 0; 117 char ch; 118 119 while ((ch = getopt(argc, argv, "gquv")) != -1) { 120 switch(ch) { 121 case 'g': 122 gflag++; 123 break; 124 case 'q': 125 qflag++; 126 break; 127 case 'u': 128 uflag++; 129 break; 130 case 'v': 131 vflag++; 132 break; 133 default: 134 usage(); 135 } 136 } 137 argc -= optind; 138 argv += optind; 139 if (!uflag && !gflag) 140 uflag++; 141 if (argc == 0) { 142 if (uflag) 143 showuid(getuid()); 144 if (gflag) { 145 mygid = getgid(); 146 ngroups = getgroups(NGROUPS, gidset); 147 if (ngroups < 0) 148 err(1, "getgroups"); 149 showgid(mygid); 150 for (i = 0; i < ngroups; i++) 151 if (gidset[i] != mygid) 152 showgid(gidset[i]); 153 } 154 return(0); 155 } 156 if (uflag && gflag) 157 usage(); 158 if (uflag) { 159 for (; argc > 0; argc--, argv++) { 160 if (alldigits(*argv)) 161 showuid(atoi(*argv)); 162 else 163 showusrname(*argv); 164 } 165 return(0); 166 } 167 if (gflag) { 168 for (; argc > 0; argc--, argv++) { 169 if (alldigits(*argv)) 170 showgid(atoi(*argv)); 171 else 172 showgrpname(*argv); 173 } 174 } 175 return(0); 176} 177 178static void 179usage(void) 180{ 181 182 fprintf(stderr, "%s\n%s\n%s\n", 183 "usage: quota [-gu] [-v | -q]", 184 " quota [-u] [-v | -q] user ...", 185 " quota -g [-v | -q] group ..."); 186 exit(1); 187} 188 189/* 190 * Print out quotas for a specified user identifier. 191 */ 192static void 193showuid(u_long uid) 194{ 195 struct passwd *pwd = getpwuid(uid); 196 u_long myuid; 197 const char *name; 198 199 if (pwd == NULL) 200 name = "(no account)"; 201 else 202 name = pwd->pw_name; 203 myuid = getuid(); 204 if (uid != myuid && myuid != 0) { 205 printf("quota: %s (uid %lu): permission denied\n", name, uid); 206 return; 207 } 208 showquotas(USRQUOTA, uid, name); 209} 210 211/* 212 * Print out quotas for a specifed user name. 213 */ 214static void 215showusrname(char *name) 216{ 217 struct passwd *pwd = getpwnam(name); 218 u_long myuid; 219 220 if (pwd == NULL) { 221 warnx("%s: unknown user", name); 222 return; 223 } 224 myuid = getuid(); 225 if (pwd->pw_uid != myuid && myuid != 0) { 226 warnx("%s (uid %u): permission denied", name, pwd->pw_uid); 227 return; 228 } 229 showquotas(USRQUOTA, pwd->pw_uid, name); 230} 231 232/* 233 * Print out quotas for a specified group identifier. 234 */ 235static void 236showgid(u_long gid) 237{ 238 struct group *grp = getgrgid(gid); 239 int ngroups; 240 gid_t mygid, gidset[NGROUPS]; 241 int i; 242 const char *name; 243 244 if (grp == NULL) 245 name = "(no entry)"; 246 else 247 name = grp->gr_name; 248 mygid = getgid(); 249 ngroups = getgroups(NGROUPS, gidset); 250 if (ngroups < 0) { 251 warn("getgroups"); 252 return; 253 } 254 if (gid != mygid) { 255 for (i = 0; i < ngroups; i++) 256 if (gid == gidset[i]) 257 break; 258 if (i >= ngroups && getuid() != 0) { 259 warnx("%s (gid %lu): permission denied", name, gid); 260 return; 261 } 262 } 263 showquotas(GRPQUOTA, gid, name); 264} 265 266/* 267 * Print out quotas for a specifed group name. 268 */ 269static void 270showgrpname(char *name) 271{ 272 struct group *grp = getgrnam(name); 273 int ngroups; 274 gid_t mygid, gidset[NGROUPS]; 275 int i; 276 277 if (grp == NULL) { 278 warnx("%s: unknown group", name); 279 return; 280 } 281 mygid = getgid(); 282 ngroups = getgroups(NGROUPS, gidset); 283 if (ngroups < 0) { 284 warn("getgroups"); 285 return; 286 } 287 if (grp->gr_gid != mygid) { 288 for (i = 0; i < ngroups; i++) 289 if (grp->gr_gid == gidset[i]) 290 break; 291 if (i >= ngroups && getuid() != 0) { 292 warnx("%s (gid %u): permission denied", name, 293 grp->gr_gid); 294 return; 295 } 296 } 297 showquotas(GRPQUOTA, grp->gr_gid, name); 298} 299 300static void 301showquotas(int type, u_long id, const char *name) 302{ 303 struct quotause *qup; 304 struct quotause *quplist; 305 const char *msgi, *msgb; 306 const char *nam; 307 int lines = 0; 308 static time_t now; 309 310 if (now == 0) 311 time(&now); 312 quplist = getprivs(id, type); 313 for (qup = quplist; qup; qup = qup->next) { 314 if (!vflag && 315 qup->dqblk.dqb_isoftlimit == 0 && 316 qup->dqblk.dqb_ihardlimit == 0 && 317 qup->dqblk.dqb_bsoftlimit == 0 && 318 qup->dqblk.dqb_bhardlimit == 0) 319 continue; 320 msgi = (char *)0; 321 if (qup->dqblk.dqb_ihardlimit && 322 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) 323 msgi = "File limit reached on"; 324 else if (qup->dqblk.dqb_isoftlimit && 325 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) { 326 if (qup->dqblk.dqb_itime > now) 327 msgi = "In file grace period on"; 328 else 329 msgi = "Over file quota on"; 330 } 331 msgb = (char *)0; 332 if (qup->dqblk.dqb_bhardlimit && 333 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) 334 msgb = "Block limit reached on"; 335 else if (qup->dqblk.dqb_bsoftlimit && 336 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) { 337 if (qup->dqblk.dqb_btime > now) 338 msgb = "In block grace period on"; 339 else 340 msgb = "Over block quota on"; 341 } 342 if (qflag) { 343 if ((msgi != (char *)0 || msgb != (char *)0) && 344 lines++ == 0) 345 heading(type, id, name, ""); 346 if (msgi != (char *)0) 347 printf("\t%s %s\n", msgi, qup->fsname); 348 if (msgb != (char *)0) 349 printf("\t%s %s\n", msgb, qup->fsname); 350 continue; 351 } 352 if (vflag || 353 qup->dqblk.dqb_curblocks || 354 qup->dqblk.dqb_curinodes) { 355 if (lines++ == 0) 356 heading(type, id, name, ""); 357 nam = qup->fsname; 358 if (strlen(qup->fsname) > 15) { 359 printf("%s\n", qup->fsname); 360 nam = ""; 361 } 362 printf("%15s%8lu%c%7lu%8lu%8s" 363 , nam 364 , (u_long) (dbtob(qup->dqblk.dqb_curblocks) 365 / 1024) 366 , (msgb == (char *)0) ? ' ' : '*' 367 , (u_long) (dbtob(qup->dqblk.dqb_bsoftlimit) 368 / 1024) 369 , (u_long) (dbtob(qup->dqblk.dqb_bhardlimit) 370 / 1024) 371 , (msgb == (char *)0) ? "" 372 :timeprt(qup->dqblk.dqb_btime)); 373 printf("%8lu%c%7lu%8lu%8s\n" 374 , (u_long)qup->dqblk.dqb_curinodes 375 , (msgi == (char *)0) ? ' ' : '*' 376 , (u_long)qup->dqblk.dqb_isoftlimit 377 , (u_long)qup->dqblk.dqb_ihardlimit 378 , (msgi == (char *)0) ? "" 379 : timeprt(qup->dqblk.dqb_itime) 380 ); 381 continue; 382 } 383 } 384 if (!qflag && lines == 0) 385 heading(type, id, name, "none"); 386} 387 388static void 389heading(int type, u_long id, const char *name, const char *tag) 390{ 391 392 printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type], 393 name, *qfextension[type], id, tag); 394 if (!qflag && tag[0] == '\0') { 395 printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n" 396 , "Filesystem" 397 , "usage" 398 , "quota" 399 , "limit" 400 , "grace" 401 , "files" 402 , "quota" 403 , "limit" 404 , "grace" 405 ); 406 } 407} 408 409/* 410 * Calculate the grace period and return a printable string for it. 411 */ 412static const char * 413timeprt(time_t seconds) 414{ 415 time_t hours, minutes; 416 static char buf[20]; 417 static time_t now; 418 419 if (now == 0) 420 time(&now); 421 if (now > seconds) 422 return ("none"); 423 seconds -= now; 424 minutes = (seconds + 30) / 60; 425 hours = (minutes + 30) / 60; 426 if (hours >= 36) { 427 sprintf(buf, "%lddays", ((long)hours + 12) / 24); 428 return (buf); 429 } 430 if (minutes >= 60) { 431 sprintf(buf, "%2ld:%ld", (long)minutes / 60, 432 (long)minutes % 60); 433 return (buf); 434 } 435 sprintf(buf, "%2ld", (long)minutes); 436 return (buf); 437} 438 439/* 440 * Collect the requested quota information. 441 */ 442static struct quotause * 443getprivs(long id, int quotatype) 444{ 445 struct quotause *qup, *quptail = NULL; 446 struct fstab *fs; 447 struct quotause *quphead; 448 struct statfs *fst; 449 int nfst, i; 450 451 qup = quphead = (struct quotause *)0; 452 453 nfst = getmntinfo(&fst, MNT_NOWAIT); 454 if (nfst == 0) 455 errx(2, "no filesystems mounted!"); 456 setfsent(); 457 for (i=0; i<nfst; i++) { 458 if (qup == NULL) { 459 if ((qup = (struct quotause *)malloc(sizeof *qup)) 460 == NULL) 461 errx(2, "out of memory"); 462 } 463 if (strcmp(fst[i].f_fstypename, "nfs") == 0) { 464 if (getnfsquota(&fst[i], qup, id, quotatype) 465 == 0) 466 continue; 467 } else if (strcmp(fst[i].f_fstypename, "ufs") == 0) { 468 /* 469 * XXX 470 * UFS filesystems must be in /etc/fstab, and must 471 * indicate that they have quotas on (?!) This is quite 472 * unlike SunOS where quotas can be enabled/disabled 473 * on a filesystem independent of /etc/fstab, and it 474 * will still print quotas for them. 475 */ 476 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL) 477 continue; 478 if (getufsquota(fs, qup, id, quotatype) == 0) 479 continue; 480 } else 481 continue; 482 strcpy(qup->fsname, fst[i].f_mntonname); 483 if (quphead == NULL) 484 quphead = qup; 485 else 486 quptail->next = qup; 487 quptail = qup; 488 quptail->next = 0; 489 qup = NULL; 490 } 491 if (qup) 492 free(qup); 493 endfsent(); 494 return (quphead); 495} 496 497/* 498 * Check to see if a particular quota is to be enabled. 499 */ 500static int 501ufshasquota(struct fstab *fs, int type, char **qfnamep) 502{ 503 static char initname, usrname[100], grpname[100]; 504 static char buf[BUFSIZ]; 505 char *opt, *cp; 506 507 if (!initname) { 508 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname); 509 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname); 510 initname = 1; 511 } 512 strcpy(buf, fs->fs_mntops); 513 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 514 if ((cp = index(opt, '='))) 515 *cp++ = '\0'; 516 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 517 break; 518 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 519 break; 520 } 521 if (!opt) 522 return (0); 523 if (cp) { 524 *qfnamep = cp; 525 return (1); 526 } 527 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 528 *qfnamep = buf; 529 return (1); 530} 531 532static int 533getufsquota(struct fstab *fs, struct quotause *qup, long id, int quotatype) 534{ 535 char *qfpathname; 536 int fd, qcmd; 537 538 qcmd = QCMD(Q_GETQUOTA, quotatype); 539 if (!ufshasquota(fs, quotatype, &qfpathname)) 540 return (0); 541 542 if (quotactl(fs->fs_file, qcmd, id, (char *)&qup->dqblk) != 0) { 543 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 544 warn("%s", qfpathname); 545 return (0); 546 } 547 (void) lseek(fd, (off_t)(id * sizeof(struct dqblk)), L_SET); 548 switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) { 549 case 0: /* EOF */ 550 /* 551 * Convert implicit 0 quota (EOF) 552 * into an explicit one (zero'ed dqblk) 553 */ 554 bzero((caddr_t)&qup->dqblk, sizeof(struct dqblk)); 555 break; 556 case sizeof(struct dqblk): /* OK */ 557 break; 558 default: /* ERROR */ 559 warn("read error: %s", qfpathname); 560 close(fd); 561 return (0); 562 } 563 close(fd); 564 } 565 return (1); 566} 567 568static int 569getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype) 570{ 571 struct getquota_args gq_args; 572 struct getquota_rslt gq_rslt; 573 struct dqblk *dqp = &qup->dqblk; 574 struct timeval tv; 575 char *cp; 576 577 if (fst->f_flags & MNT_LOCAL) 578 return (0); 579 580 /* 581 * rpc.rquotad does not support group quotas 582 */ 583 if (quotatype != USRQUOTA) 584 return (0); 585 586 /* 587 * must be some form of "hostname:/path" 588 */ 589 cp = strchr(fst->f_mntfromname, ':'); 590 if (cp == NULL) { 591 warnx("cannot find hostname for %s", fst->f_mntfromname); 592 return (0); 593 } 594 595 *cp = '\0'; 596 if (*(cp+1) != '/') { 597 *cp = ':'; 598 return (0); 599 } 600 601 /* Avoid attempting the RPC for special amd(8) filesystems. */ 602 if (strncmp(fst->f_mntfromname, "pid", 3) == 0 && 603 strchr(fst->f_mntfromname, '@') != NULL) { 604 *cp = ':'; 605 return (0); 606 } 607 608 gq_args.gqa_pathp = cp + 1; 609 gq_args.gqa_uid = id; 610 if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS, 611 RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_getquota_args, (char *)&gq_args, 612 (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt) != 0) { 613 *cp = ':'; 614 return (0); 615 } 616 617 switch (gq_rslt.status) { 618 case Q_NOQUOTA: 619 break; 620 case Q_EPERM: 621 warnx("quota permission error, host: %s", 622 fst->f_mntfromname); 623 break; 624 case Q_OK: 625 gettimeofday(&tv, NULL); 626 /* blocks*/ 627 dqp->dqb_bhardlimit = 628 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit * 629 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE; 630 dqp->dqb_bsoftlimit = 631 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit * 632 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE; 633 dqp->dqb_curblocks = 634 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks * 635 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE; 636 /* inodes */ 637 dqp->dqb_ihardlimit = 638 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit; 639 dqp->dqb_isoftlimit = 640 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit; 641 dqp->dqb_curinodes = 642 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles; 643 /* grace times */ 644 dqp->dqb_btime = 645 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft; 646 dqp->dqb_itime = 647 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft; 648 *cp = ':'; 649 return (1); 650 default: 651 warnx("bad rpc result, host: %s", fst->f_mntfromname); 652 break; 653 } 654 *cp = ':'; 655 return (0); 656} 657 658static int 659callaurpc(char *host, int prognum, int versnum, int procnum, 660 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) 661{ 662 struct sockaddr_in server_addr; 663 enum clnt_stat clnt_stat; 664 struct hostent *hp; 665 struct timeval timeout, tottimeout; 666 667 CLIENT *client = NULL; 668 int sock = RPC_ANYSOCK; 669 670 if ((hp = gethostbyname(host)) == NULL) 671 return ((int) RPC_UNKNOWNHOST); 672 timeout.tv_usec = 0; 673 timeout.tv_sec = 6; 674 bcopy(hp->h_addr, &server_addr.sin_addr, 675 MIN(hp->h_length,(int)sizeof(server_addr.sin_addr))); 676 server_addr.sin_family = AF_INET; 677 server_addr.sin_port = 0; 678 679 if ((client = clntudp_create(&server_addr, prognum, 680 versnum, timeout, &sock)) == NULL) 681 return ((int) rpc_createerr.cf_stat); 682 683 client->cl_auth = authunix_create_default(); 684 tottimeout.tv_sec = 25; 685 tottimeout.tv_usec = 0; 686 clnt_stat = clnt_call(client, procnum, inproc, in, 687 outproc, out, tottimeout); 688 689 return ((int) clnt_stat); 690} 691 692static int 693alldigits(char *s) 694{ 695 int c; 696 697 c = *s++; 698 do { 699 if (!isdigit(c)) 700 return (0); 701 } while ((c = *s++)); 702 return (1); 703} 704