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 34 static 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 40 static 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$"); 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 76 const char *qfname = QUOTAFILENAME; 77 const char *qfextension[] = INITQFNAMES; 78 79 struct quotause { 80 struct quotause *next; 81 long flags; 82 struct dqblk dqblk; 83 char fsname[MAXPATHLEN + 1]; 84 }; 85 86 static char *timeprt(int64_t seconds); 87 static struct quotause *getprivs(long id, int quotatype); 88 static void usage(void); 89 static int showuid(u_long uid); 90 static int showgid(u_long gid); 91 static int showusrname(char *name); 92 static int showgrpname(char *name); 93 static int showquotas(int type, u_long id, const char *name); 94 static void showrawquotas(int type, u_long id, struct quotause *qup); 95 static void heading(int type, u_long id, const char *name, const char *tag); 96 static int getufsquota(struct fstab *fs, struct quotause *qup, long id, 97 int quotatype); 98 static int getnfsquota(struct statfs *fst, struct quotause *qup, long id, 99 int quotatype); 100 static int callaurpc(char *host, int prognum, int versnum, int procnum, 101 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out); 102 static int alldigits(char *s); 103 104 int hflag; 105 int lflag; 106 int rflag; 107 int qflag; 108 int vflag; 109 char *filename = NULL; 110 111 int 112 main(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 189 static void 190 usage(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 */ 203 static int 204 showuid(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 */ 219 static int 220 showusrname(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 */ 234 static int 235 showgid(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 */ 250 static int 251 showgrpname(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 262 static void 263 prthumanval(int len, u_int64_t bytes) 264 { 265 char buf[len + 1]; 266 267 humanize_number(buf, sizeof(buf), bytes, "", HN_AUTOSCALE, 268 HN_B | HN_NOSPACE | HN_DECIMAL); 269 270 (void)printf(" %*s", len, buf); 271 } 272 273 static int 274 showquotas(int type, u_long id, const char *name) 275 { 276 struct quotause *qup; 277 struct quotause *quplist; 278 const char *msgi, *msgb; 279 const char *nam; 280 char *bgrace = NULL, *igrace = NULL; 281 int lines = 0, overquota = 0; 282 static time_t now; 283 284 if (now == 0) 285 time(&now); 286 quplist = getprivs(id, type); 287 for (qup = quplist; qup; qup = qup->next) { 288 msgi = NULL; 289 if (qup->dqblk.dqb_ihardlimit && 290 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) { 291 overquota++; 292 msgi = "File limit reached on"; 293 } 294 else if (qup->dqblk.dqb_isoftlimit && 295 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) { 296 overquota++; 297 if (qup->dqblk.dqb_itime > now) 298 msgi = "In file grace period on"; 299 else 300 msgi = "Over file quota on"; 301 } 302 msgb = NULL; 303 if (qup->dqblk.dqb_bhardlimit && 304 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) { 305 overquota++; 306 msgb = "Block limit reached on"; 307 } 308 else if (qup->dqblk.dqb_bsoftlimit && 309 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) { 310 overquota++; 311 if (qup->dqblk.dqb_btime > now) 312 msgb = "In block grace period on"; 313 else 314 msgb = "Over block quota on"; 315 } 316 if (rflag) { 317 showrawquotas(type, id, qup); 318 continue; 319 } 320 if (!vflag && 321 qup->dqblk.dqb_isoftlimit == 0 && 322 qup->dqblk.dqb_ihardlimit == 0 && 323 qup->dqblk.dqb_bsoftlimit == 0 && 324 qup->dqblk.dqb_bhardlimit == 0) 325 continue; 326 if (qflag) { 327 if ((msgi != NULL || msgb != NULL) && 328 lines++ == 0) 329 heading(type, id, name, ""); 330 if (msgi != NULL) 331 printf("\t%s %s\n", msgi, qup->fsname); 332 if (msgb != NULL) 333 printf("\t%s %s\n", msgb, qup->fsname); 334 continue; 335 } 336 if (!vflag && 337 qup->dqblk.dqb_curblocks == 0 && 338 qup->dqblk.dqb_curinodes == 0) 339 continue; 340 if (lines++ == 0) 341 heading(type, id, name, ""); 342 nam = qup->fsname; 343 if (strlen(qup->fsname) > 15) { 344 printf("%s\n", qup->fsname); 345 nam = ""; 346 } 347 printf("%-15s", nam); 348 if (hflag) { 349 prthumanval(7, dbtob(qup->dqblk.dqb_curblocks)); 350 printf("%c", (msgb == NULL) ? ' ' : '*'); 351 prthumanval(7, dbtob(qup->dqblk.dqb_bsoftlimit)); 352 prthumanval(7, dbtob(qup->dqblk.dqb_bhardlimit)); 353 } else { 354 printf(" %7ju%c %7ju %7ju", 355 dbtob(1024) * (uintmax_t)qup->dqblk.dqb_curblocks, 356 (msgb == NULL) ? ' ' : '*', 357 dbtob(1024) * (uintmax_t)qup->dqblk.dqb_bsoftlimit, 358 dbtob(1024) * (uintmax_t)qup->dqblk.dqb_bhardlimit); 359 } 360 if (msgb != NULL) 361 bgrace = timeprt(qup->dqblk.dqb_btime); 362 if (msgi != NULL) 363 igrace = timeprt(qup->dqblk.dqb_itime); 364 printf("%8s %6ju%c %6ju %6ju%8s\n" 365 , (msgb == NULL) ? "" : bgrace 366 , (uintmax_t)qup->dqblk.dqb_curinodes 367 , (msgi == NULL) ? ' ' : '*' 368 , (uintmax_t)qup->dqblk.dqb_isoftlimit 369 , (uintmax_t)qup->dqblk.dqb_ihardlimit 370 , (msgi == NULL) ? "" : igrace 371 ); 372 if (msgb != NULL) 373 free(bgrace); 374 if (msgi != NULL) 375 free(igrace); 376 } 377 if (!qflag && !rflag && lines == 0) 378 heading(type, id, name, "none"); 379 return (overquota); 380 } 381 382 static void 383 showrawquotas(int type, u_long id, struct quotause *qup) 384 { 385 time_t t; 386 387 printf("Raw %s quota information for id %lu on %s\n", 388 type == USRQUOTA ? "user" : "group", id, qup->fsname); 389 printf("block hard limit: %ju\n", 390 (uintmax_t)qup->dqblk.dqb_bhardlimit); 391 printf("block soft limit: %ju\n", 392 (uintmax_t)qup->dqblk.dqb_bsoftlimit); 393 printf("current block count: %ju\n", 394 (uintmax_t)qup->dqblk.dqb_curblocks); 395 printf("i-node hard limit: %ju\n", 396 (uintmax_t)qup->dqblk.dqb_ihardlimit); 397 printf("i-node soft limit: %ju\n", 398 (uintmax_t)qup->dqblk.dqb_isoftlimit); 399 printf("current i-node count: %ju\n", 400 (uintmax_t)qup->dqblk.dqb_curinodes); 401 printf("block grace time: %jd", 402 (intmax_t)qup->dqblk.dqb_btime); 403 if (qup->dqblk.dqb_btime != 0) { 404 t = qup->dqblk.dqb_btime; 405 printf(" %s", ctime(&t)); 406 } else { 407 printf("\n"); 408 } 409 printf("i-node grace time: %jd", (intmax_t)qup->dqblk.dqb_itime); 410 if (qup->dqblk.dqb_itime != 0) { 411 t = qup->dqblk.dqb_itime; 412 printf(" %s", ctime(&t)); 413 } else { 414 printf("\n"); 415 } 416 } 417 418 419 static void 420 heading(int type, u_long id, const char *name, const char *tag) 421 { 422 423 printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type], 424 name, *qfextension[type], id, tag); 425 if (!qflag && tag[0] == '\0') { 426 printf("%-15s %7s %8s %7s %7s %6s %7s %6s%8s\n" 427 , "Filesystem" 428 , "usage" 429 , "quota" 430 , "limit" 431 , "grace" 432 , "files" 433 , "quota" 434 , "limit" 435 , "grace" 436 ); 437 } 438 } 439 440 /* 441 * Calculate the grace period and return a printable string for it. 442 */ 443 static char * 444 timeprt(int64_t seconds) 445 { 446 time_t hours, minutes; 447 char *buf; 448 static time_t now; 449 450 if (now == 0) 451 time(&now); 452 if (now > seconds) { 453 if ((buf = strdup("none")) == NULL) 454 errx(1, "strdup() failed in timeprt()"); 455 return (buf); 456 } 457 seconds -= now; 458 minutes = (seconds + 30) / 60; 459 hours = (minutes + 30) / 60; 460 if (hours >= 36) { 461 if (asprintf(&buf, "%lddays", ((long)hours + 12) / 24) < 0) 462 errx(1, "asprintf() failed in timeprt(1)"); 463 return (buf); 464 } 465 if (minutes >= 60) { 466 if (asprintf(&buf, "%2ld:%ld", (long)minutes / 60, 467 (long)minutes % 60) < 0) 468 errx(1, "asprintf() failed in timeprt(2)"); 469 return (buf); 470 } 471 if (asprintf(&buf, "%2ld", (long)minutes) < 0) 472 errx(1, "asprintf() failed in timeprt(3)"); 473 return (buf); 474 } 475 476 /* 477 * Collect the requested quota information. 478 */ 479 static struct quotause * 480 getprivs(long id, int quotatype) 481 { 482 struct quotause *qup, *quptail = NULL; 483 struct fstab *fs; 484 struct quotause *quphead; 485 struct statfs *fst; 486 int nfst, i; 487 struct statfs sfb; 488 489 qup = quphead = (struct quotause *)0; 490 491 if (filename != NULL && statfs(filename, &sfb) != 0) 492 err(1, "cannot statfs %s", filename); 493 nfst = getmntinfo(&fst, MNT_NOWAIT); 494 if (nfst == 0) 495 errx(2, "no filesystems mounted!"); 496 setfsent(); 497 for (i = 0; i < nfst; i++) { 498 if (qup == NULL) { 499 if ((qup = (struct quotause *)malloc(sizeof *qup)) 500 == NULL) 501 errx(2, "out of memory"); 502 } 503 /* 504 * See if the user requested a specific file system 505 * or specified a file inside a mounted file system. 506 */ 507 if (filename != NULL && 508 strcmp(sfb.f_mntonname, fst[i].f_mntonname) != 0) 509 continue; 510 if (strcmp(fst[i].f_fstypename, "nfs") == 0) { 511 if (lflag) 512 continue; 513 if (getnfsquota(&fst[i], qup, id, quotatype) == 0) 514 continue; 515 } else if (strcmp(fst[i].f_fstypename, "ufs") == 0) { 516 /* 517 * XXX 518 * UFS filesystems must be in /etc/fstab, and must 519 * indicate that they have quotas on (?!) This is quite 520 * unlike SunOS where quotas can be enabled/disabled 521 * on a filesystem independent of /etc/fstab, and it 522 * will still print quotas for them. 523 */ 524 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL) 525 continue; 526 if (getufsquota(fs, qup, id, quotatype) == 0) 527 continue; 528 } else 529 continue; 530 strcpy(qup->fsname, fst[i].f_mntonname); 531 if (quphead == NULL) 532 quphead = qup; 533 else 534 quptail->next = qup; 535 quptail = qup; 536 quptail->next = 0; 537 qup = NULL; 538 } 539 if (qup) 540 free(qup); 541 endfsent(); 542 return (quphead); 543 } 544 545 /* 546 * Check to see if a particular quota is available. 547 */ 548 static int 549 getufsquota(struct fstab *fs, struct quotause *qup, long id, int quotatype) 550 { 551 struct quotafile *qf; 552 553 if ((qf = quota_open(fs, quotatype, O_RDONLY)) == NULL) 554 return (0); 555 if (quota_read(qf, &qup->dqblk, id) != 0) 556 return (0); 557 quota_close(qf); 558 return (1); 559 } 560 561 static int 562 getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype) 563 { 564 struct getquota_args gq_args; 565 struct getquota_rslt gq_rslt; 566 struct dqblk *dqp = &qup->dqblk; 567 struct timeval tv; 568 char *cp; 569 570 if (fst->f_flags & MNT_LOCAL) 571 return (0); 572 573 /* 574 * rpc.rquotad does not support group quotas 575 */ 576 if (quotatype != USRQUOTA) 577 return (0); 578 579 /* 580 * must be some form of "hostname:/path" 581 */ 582 cp = strchr(fst->f_mntfromname, ':'); 583 if (cp == NULL) { 584 warnx("cannot find hostname for %s", fst->f_mntfromname); 585 return (0); 586 } 587 588 *cp = '\0'; 589 if (*(cp+1) != '/') { 590 *cp = ':'; 591 return (0); 592 } 593 594 /* Avoid attempting the RPC for special amd(8) filesystems. */ 595 if (strncmp(fst->f_mntfromname, "pid", 3) == 0 && 596 strchr(fst->f_mntfromname, '@') != NULL) { 597 *cp = ':'; 598 return (0); 599 } 600 601 gq_args.gqa_pathp = cp + 1; 602 gq_args.gqa_uid = id; 603 if (callaurpc(fst->f_mntfromname, RQUOTAPROG, RQUOTAVERS, 604 RQUOTAPROC_GETQUOTA, (xdrproc_t)xdr_getquota_args, (char *)&gq_args, 605 (xdrproc_t)xdr_getquota_rslt, (char *)&gq_rslt) != 0) { 606 *cp = ':'; 607 return (0); 608 } 609 610 switch (gq_rslt.status) { 611 case Q_NOQUOTA: 612 break; 613 case Q_EPERM: 614 warnx("quota permission error, host: %s", 615 fst->f_mntfromname); 616 break; 617 case Q_OK: 618 gettimeofday(&tv, NULL); 619 /* blocks*/ 620 dqp->dqb_bhardlimit = 621 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bhardlimit * 622 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 623 dqp->dqb_bsoftlimit = 624 gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsoftlimit * 625 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 626 dqp->dqb_curblocks = 627 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curblocks * 628 (gq_rslt.getquota_rslt_u.gqr_rquota.rq_bsize / DEV_BSIZE); 629 /* inodes */ 630 dqp->dqb_ihardlimit = 631 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fhardlimit; 632 dqp->dqb_isoftlimit = 633 gq_rslt.getquota_rslt_u.gqr_rquota.rq_fsoftlimit; 634 dqp->dqb_curinodes = 635 gq_rslt.getquota_rslt_u.gqr_rquota.rq_curfiles; 636 /* grace times */ 637 dqp->dqb_btime = 638 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_btimeleft; 639 dqp->dqb_itime = 640 tv.tv_sec + gq_rslt.getquota_rslt_u.gqr_rquota.rq_ftimeleft; 641 *cp = ':'; 642 return (1); 643 default: 644 warnx("bad rpc result, host: %s", fst->f_mntfromname); 645 break; 646 } 647 *cp = ':'; 648 return (0); 649 } 650 651 static int 652 callaurpc(char *host, int prognum, int versnum, int procnum, 653 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) 654 { 655 struct sockaddr_in server_addr; 656 enum clnt_stat clnt_stat; 657 struct hostent *hp; 658 struct timeval timeout, tottimeout; 659 660 CLIENT *client = NULL; 661 int sock = RPC_ANYSOCK; 662 663 if ((hp = gethostbyname(host)) == NULL) 664 return ((int) RPC_UNKNOWNHOST); 665 timeout.tv_usec = 0; 666 timeout.tv_sec = 6; 667 bcopy(hp->h_addr, &server_addr.sin_addr, 668 MIN(hp->h_length,(int)sizeof(server_addr.sin_addr))); 669 server_addr.sin_family = AF_INET; 670 server_addr.sin_port = 0; 671 672 if ((client = clntudp_create(&server_addr, prognum, 673 versnum, timeout, &sock)) == NULL) 674 return ((int) rpc_createerr.cf_stat); 675 676 client->cl_auth = authunix_create_default(); 677 tottimeout.tv_sec = 25; 678 tottimeout.tv_usec = 0; 679 clnt_stat = clnt_call(client, procnum, inproc, in, 680 outproc, out, tottimeout); 681 682 return ((int) clnt_stat); 683 } 684 685 static int 686 alldigits(char *s) 687 { 688 int c; 689 690 c = *s++; 691 do { 692 if (!isdigit(c)) 693 return (0); 694 } while ((c = *s++)); 695 return (1); 696 } 697