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