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