1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 /* 30 * University Copyright- Copyright (c) 1982, 1986, 1988 31 * The Regents of the University of California 32 * All Rights Reserved 33 * 34 * University Acknowledgment- Portions of this document are derived from 35 * software developed by the University of California, Berkeley, and its 36 * contributors. 37 */ 38 39 #pragma ident "%Z%%M% %I% %E% SMI" 40 41 /* 42 * Disk quota reporting program. 43 */ 44 #include <stdio.h> 45 #include <sys/mnttab.h> 46 #include <ctype.h> 47 #include <pwd.h> 48 #include <errno.h> 49 #include <fcntl.h> 50 #include <memory.h> 51 #include <sys/time.h> 52 #include <sys/param.h> 53 #include <sys/types.h> 54 #include <sys/sysmacros.h> 55 #include <sys/mntent.h> 56 #include <sys/file.h> 57 #include <sys/stat.h> 58 #include <sys/fs/ufs_quota.h> 59 60 int vflag; 61 int nolocalquota; 62 63 extern int optind; 64 extern char *optarg; 65 66 #define QFNAME "quotas" 67 68 #if DEV_BSIZE < 1024 69 #define kb(x) ((x) / (1024 / DEV_BSIZE)) 70 #else 71 #define kb(x) ((x) * (DEV_BSIZE / 1024)) 72 #endif 73 74 static int getnfsquota(char *, char *, uid_t, struct dqblk *); 75 static void showuid(uid_t); 76 static void showquotas(uid_t, char *); 77 static void warn(struct mnttab *, struct dqblk *); 78 static void heading(uid_t, char *); 79 static void prquota(struct mnttab *, struct dqblk *); 80 static void fmttime(char *, long); 81 82 int 83 main(int argc, char *argv[]) 84 { 85 int opt; 86 int i; 87 int status = 0; 88 89 while ((opt = getopt(argc, argv, "vV")) != EOF) { 90 switch (opt) { 91 92 case 'v': 93 vflag++; 94 break; 95 96 case 'V': /* Print command line */ 97 { 98 char *opt_text; 99 int opt_count; 100 101 (void) fprintf(stdout, "quota -F UFS "); 102 for (opt_count = 1; opt_count < argc; opt_count++) { 103 opt_text = argv[opt_count]; 104 if (opt_text) 105 (void) fprintf(stdout, " %s ", opt_text); 106 } 107 (void) fprintf(stdout, "\n"); 108 } 109 break; 110 111 case '?': 112 fprintf(stderr, "quota: %c: unknown option\n", 113 opt); 114 fprintf(stderr, "ufs usage: quota [-v] [username]\n"); 115 exit(32); 116 } 117 } 118 if (quotactl(Q_ALLSYNC, NULL, (uid_t)0, NULL) < 0 && errno == EINVAL) { 119 if (vflag) 120 fprintf(stderr, "There are no quotas on this system\n"); 121 nolocalquota++; 122 } 123 if (argc == optind) { 124 showuid(getuid()); 125 exit(0); 126 } 127 for (i = optind; i < argc; i++) { 128 if (alldigits(argv[i])) { 129 showuid((uid_t)atoi(argv[i])); 130 } else 131 status |= showname(argv[i]); 132 } 133 return (status); 134 } 135 136 static void 137 showuid(uid_t uid) 138 { 139 struct passwd *pwd = getpwuid(uid); 140 141 if (uid == 0) { 142 if (vflag) 143 printf("no disk quota for uid 0\n"); 144 return; 145 } 146 if (pwd == NULL) 147 showquotas(uid, "(no account)"); 148 else 149 showquotas(uid, pwd->pw_name); 150 } 151 152 int 153 showname(char *name) 154 { 155 struct passwd *pwd = getpwnam(name); 156 157 if (pwd == NULL) { 158 fprintf(stderr, "quota: %s: unknown user\n", name); 159 return (32); 160 } 161 if (pwd->pw_uid == 0) { 162 if (vflag) 163 printf("no disk quota for %s (uid 0)\n", name); 164 return (0); 165 } 166 showquotas(pwd->pw_uid, name); 167 return (0); 168 } 169 170 #include "../../nfs/lib/replica.h" 171 172 static void 173 showquotas(uid_t uid, char *name) 174 { 175 struct mnttab mnt; 176 FILE *mtab; 177 struct dqblk dqblk; 178 uid_t myuid; 179 180 myuid = getuid(); 181 if (uid != myuid && myuid != 0) { 182 printf("quota: %s (uid %d): permission denied\n", name, uid); 183 exit(32); 184 } 185 if (vflag) 186 heading(uid, name); 187 mtab = fopen(MNTTAB, "r"); 188 while (getmntent(mtab, &mnt) == NULL) { 189 if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) == 0) { 190 if (nolocalquota || 191 (quotactl(Q_GETQUOTA, 192 mnt.mnt_mountp, uid, &dqblk) != 0 && 193 !(vflag && getdiskquota(&mnt, uid, &dqblk)))) 194 continue; 195 } else if (strcmp(mnt.mnt_fstype, MNTTYPE_NFS) == 0) { 196 197 struct replica *rl; 198 int count; 199 200 if (hasopt(MNTOPT_NOQUOTA, mnt.mnt_mntopts)) 201 continue; 202 203 /* 204 * Skip quota processing if mounted with public 205 * option. We are not likely to be able to pierce 206 * a fire wall to contact the quota server. 207 */ 208 if (hasopt(MNTOPT_PUBLIC, mnt.mnt_mntopts)) 209 continue; 210 211 rl = parse_replica(mnt.mnt_special, &count); 212 213 if (rl == NULL) { 214 215 if (count < 0) 216 fprintf(stderr, "cannot find hostname " 217 "and/or pathname for %s\n", 218 mnt.mnt_mountp); 219 else 220 fprintf(stderr, "no memory to parse " 221 "mnttab entry for %s\n", 222 mnt.mnt_mountp); 223 continue; 224 } 225 226 /* 227 * We skip quota reporting on mounts with replicas 228 * for the following reasons: 229 * 230 * (1) Very little point in reporting quotas on 231 * a set of read-only replicas ... how will the 232 * user correct the problem? 233 * 234 * (2) Which replica would we report the quota 235 * for? If we pick the current replica, what 236 * happens when a fail over event occurs? The 237 * next time quota is run, the quota will look 238 * all different, or there won't even be one. 239 * This has the potential to break scripts. 240 * 241 * If we prnt quouta for all replicas, how do 242 * we present the output without breaking scripts? 243 */ 244 245 if (count > 1) { 246 free_replica(rl, count); 247 continue; 248 } 249 250 /* 251 * Skip file systems mounted using public fh. 252 * We are not likely to be able to pierce 253 * a fire wall to contact the quota server. 254 */ 255 if (strcmp(rl[0].host, "nfs") == 0 && 256 strncmp(rl[0].path, "//", 2) == 0) { 257 free_replica(rl, count); 258 continue; 259 } 260 261 if (!getnfsquota(rl[0].host, rl[0].path, uid, &dqblk)) { 262 free_replica(rl, count); 263 continue; 264 } 265 266 free_replica(rl, count); 267 268 } else { 269 continue; 270 } 271 if (dqblk.dqb_bsoftlimit == 0 && dqblk.dqb_bhardlimit == 0 && 272 dqblk.dqb_fsoftlimit == 0 && dqblk.dqb_fhardlimit == 0) 273 continue; 274 if (vflag) 275 prquota(&mnt, &dqblk); 276 else 277 warn(&mnt, &dqblk); 278 } 279 fclose(mtab); 280 } 281 282 static void 283 warn(struct mnttab *mntp, struct dqblk *dqp) 284 { 285 struct timeval tv; 286 287 time(&(tv.tv_sec)); 288 tv.tv_usec = 0; 289 if (dqp->dqb_bhardlimit && 290 dqp->dqb_curblocks >= dqp->dqb_bhardlimit) { 291 printf("Block limit reached on %s\n", mntp->mnt_mountp); 292 } else if (dqp->dqb_bsoftlimit && 293 dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { 294 if (dqp->dqb_btimelimit == 0) { 295 printf("Over disk quota on %s, remove %luK\n", 296 mntp->mnt_mountp, 297 kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); 298 } else if (dqp->dqb_btimelimit > tv.tv_sec) { 299 char btimeleft[80]; 300 301 fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); 302 printf("Over disk quota on %s, remove %luK within %s\n", 303 mntp->mnt_mountp, 304 kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1), 305 btimeleft); 306 } else { 307 printf( 308 "Over disk quota on %s, time limit has expired, remove %luK\n", 309 mntp->mnt_mountp, 310 kb(dqp->dqb_curblocks - dqp->dqb_bsoftlimit + 1)); 311 } 312 } 313 if (dqp->dqb_fhardlimit && 314 dqp->dqb_curfiles >= dqp->dqb_fhardlimit) { 315 printf("File count limit reached on %s\n", mntp->mnt_mountp); 316 } else if (dqp->dqb_fsoftlimit && 317 dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { 318 if (dqp->dqb_ftimelimit == 0) { 319 printf("Over file quota on %s, remove %lu file%s\n", 320 mntp->mnt_mountp, 321 dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, 322 ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? 323 "s" : "")); 324 } else if (dqp->dqb_ftimelimit > tv.tv_sec) { 325 char ftimeleft[80]; 326 327 fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); 328 printf( 329 "Over file quota on %s, remove %lu file%s within %s\n", 330 mntp->mnt_mountp, 331 dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, 332 ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? 333 "s" : ""), ftimeleft); 334 } else { 335 printf( 336 "Over file quota on %s, time limit has expired, remove %lu file%s\n", 337 mntp->mnt_mountp, 338 dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1, 339 ((dqp->dqb_curfiles - dqp->dqb_fsoftlimit + 1) > 1 ? 340 "s" : "")); 341 } 342 } 343 } 344 345 static void 346 heading(uid_t uid, char *name) 347 { 348 printf("Disk quotas for %s (uid %ld):\n", name, (long)uid); 349 printf("%-12s %7s%7s%7s%12s%7s%7s%7s%12s\n", 350 "Filesystem", 351 "usage", 352 "quota", 353 "limit", 354 "timeleft", 355 "files", 356 "quota", 357 "limit", 358 "timeleft"); 359 } 360 361 static void 362 prquota(struct mnttab *mntp, struct dqblk *dqp) 363 { 364 struct timeval tv; 365 char ftimeleft[80], btimeleft[80]; 366 char *cp; 367 368 time(&(tv.tv_sec)); 369 tv.tv_usec = 0; 370 if (dqp->dqb_bsoftlimit && dqp->dqb_curblocks >= dqp->dqb_bsoftlimit) { 371 if (dqp->dqb_btimelimit == 0) { 372 strcpy(btimeleft, "NOT STARTED"); 373 } else if (dqp->dqb_btimelimit > tv.tv_sec) { 374 fmttime(btimeleft, dqp->dqb_btimelimit - tv.tv_sec); 375 } else { 376 strcpy(btimeleft, "EXPIRED"); 377 } 378 } else { 379 btimeleft[0] = '\0'; 380 } 381 if (dqp->dqb_fsoftlimit && dqp->dqb_curfiles >= dqp->dqb_fsoftlimit) { 382 if (dqp->dqb_ftimelimit == 0) { 383 strcpy(ftimeleft, "NOT STARTED"); 384 } else if (dqp->dqb_ftimelimit > tv.tv_sec) { 385 fmttime(ftimeleft, dqp->dqb_ftimelimit - tv.tv_sec); 386 } else { 387 strcpy(ftimeleft, "EXPIRED"); 388 } 389 } else { 390 ftimeleft[0] = '\0'; 391 } 392 if (strlen(mntp->mnt_mountp) > 12) { 393 printf("%s\n", mntp->mnt_mountp); 394 cp = ""; 395 } else { 396 cp = mntp->mnt_mountp; 397 } 398 printf("%-12.12s %7d %6d %6d %11s %6d %6d %6d %11s\n", 399 cp, 400 kb(dqp->dqb_curblocks), 401 kb(dqp->dqb_bsoftlimit), 402 kb(dqp->dqb_bhardlimit), 403 btimeleft, 404 dqp->dqb_curfiles, 405 dqp->dqb_fsoftlimit, 406 dqp->dqb_fhardlimit, 407 ftimeleft); 408 } 409 410 static void 411 fmttime(char *buf, long time) 412 { 413 int i; 414 static struct { 415 int c_secs; /* conversion units in secs */ 416 char *c_str; /* unit string */ 417 } cunits [] = { 418 {60*60*24*28, "months"}, 419 {60*60*24*7, "weeks"}, 420 {60*60*24, "days"}, 421 {60*60, "hours"}, 422 {60, "mins"}, 423 {1, "secs"} 424 }; 425 426 if (time <= 0) { 427 strcpy(buf, "EXPIRED"); 428 return; 429 } 430 for (i = 0; i < sizeof (cunits)/sizeof (cunits[0]); i++) { 431 if (time >= cunits[i].c_secs) 432 break; 433 } 434 sprintf(buf, "%.1f %s", (double)time/cunits[i].c_secs, cunits[i].c_str); 435 } 436 437 int 438 alldigits(char *s) 439 { 440 int c; 441 442 c = *s++; 443 do { 444 if (!isdigit(c)) 445 return (0); 446 } while (c = *s++); 447 return (1); 448 } 449 450 int 451 getdiskquota(struct mnttab *mntp, uid_t uid, struct dqblk *dqp) 452 { 453 int fd; 454 dev_t fsdev; 455 struct stat64 statb; 456 char qfilename[MAXPATHLEN]; 457 458 if (stat64(mntp->mnt_special, &statb) < 0 || 459 (statb.st_mode & S_IFMT) != S_IFBLK) 460 return (0); 461 fsdev = statb.st_rdev; 462 (void) snprintf(qfilename, sizeof (qfilename), "%s/%s", 463 mntp->mnt_mountp, QFNAME); 464 if (stat64(qfilename, &statb) < 0 || statb.st_dev != fsdev) 465 return (0); 466 if ((fd = open64(qfilename, O_RDONLY)) < 0) 467 return (0); 468 (void) llseek(fd, (offset_t)dqoff(uid), L_SET); 469 switch (read(fd, dqp, sizeof (struct dqblk))) { 470 case 0: /* EOF */ 471 /* 472 * Convert implicit 0 quota (EOF) 473 * into an explicit one (zero'ed dqblk). 474 */ 475 memset((caddr_t)dqp, 0, sizeof (struct dqblk)); 476 break; 477 478 case sizeof (struct dqblk): /* OK */ 479 break; 480 481 default: /* ERROR */ 482 close(fd); 483 return (0); 484 } 485 close(fd); 486 return (1); 487 } 488 489 int 490 quotactl(int cmd, char *mountp, uid_t uid, caddr_t addr) 491 { 492 int fd; 493 int status; 494 struct quotctl quota; 495 char qfile[MAXPATHLEN]; 496 497 FILE *fstab; 498 struct mnttab mnt; 499 500 501 if ((mountp == NULL) && (cmd == Q_ALLSYNC)) { 502 /* 503 * Find the mount point of any mounted file system. This is 504 * because the ioctl that implements the quotactl call has 505 * to go to a real file, and not to the block device. 506 */ 507 if ((fstab = fopen(MNTTAB, "r")) == NULL) { 508 fprintf(stderr, "%s: ", MNTTAB); 509 perror("open"); 510 exit(32); 511 } 512 fd = -1; 513 while ((status = getmntent(fstab, &mnt)) == NULL) { 514 if (strcmp(mnt.mnt_fstype, MNTTYPE_UFS) != 0 || 515 hasopt(MNTOPT_RO, mnt.mnt_mntopts)) 516 continue; 517 if ((strlcpy(qfile, mnt.mnt_mountp, 518 sizeof (qfile)) >= sizeof (qfile)) || 519 (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= 520 sizeof (qfile))) { 521 continue; 522 } 523 if ((fd = open64(qfile, O_RDONLY)) != -1) 524 break; 525 } 526 fclose(fstab); 527 if (fd == -1) { 528 errno = ENOENT; 529 return (-1); 530 } 531 } else { 532 if (mountp == NULL || mountp[0] == '\0') { 533 errno = ENOENT; 534 return (-1); 535 } 536 if ((strlcpy(qfile, mountp, sizeof (qfile)) >= sizeof 537 (qfile)) || 538 (strlcat(qfile, "/" QFNAME, sizeof (qfile)) >= sizeof 539 (qfile))) { 540 errno = ENOENT; 541 return (-1); 542 } 543 if ((fd = open64(qfile, O_RDONLY)) < 0) 544 return (-1); 545 } /* else */ 546 quota.op = cmd; 547 quota.uid = uid; 548 quota.addr = addr; 549 status = ioctl(fd, Q_QUOTACTL, "a); 550 if (fd != 0) 551 close(fd); 552 return (status); 553 } 554 555 556 /* 557 * Return 1 if opt appears in optlist 558 */ 559 int 560 hasopt(char *opt, char *optlist) 561 { 562 char *value; 563 char *opts[2]; 564 565 opts[0] = opt; 566 opts[1] = NULL; 567 568 if (optlist == NULL) 569 return (0); 570 while (*optlist != '\0') { 571 if (getsubopt(&optlist, opts, &value) == 0) 572 return (1); 573 } 574 return (0); 575 } 576 577 #include <rpc/rpc.h> 578 #include <netdb.h> 579 #include <rpcsvc/rquota.h> 580 581 static int 582 getnfsquota(char *hostp, char *path, uid_t uid, struct dqblk *dqp) 583 { 584 struct getquota_args gq_args; 585 struct getquota_rslt gq_rslt; 586 struct rquota *rquota; 587 extern char *strchr(); 588 589 gq_args.gqa_pathp = path; 590 gq_args.gqa_uid = uid; 591 if (callaurpc(hostp, RQUOTAPROG, RQUOTAVERS, 592 (vflag? RQUOTAPROC_GETQUOTA: RQUOTAPROC_GETACTIVEQUOTA), 593 xdr_getquota_args, &gq_args, xdr_getquota_rslt, &gq_rslt) != 0) { 594 return (0); 595 } 596 switch (gq_rslt.status) { 597 case Q_OK: 598 { 599 struct timeval tv; 600 u_longlong_t limit; 601 602 rquota = &gq_rslt.getquota_rslt_u.gqr_rquota; 603 604 if (!vflag && rquota->rq_active == FALSE) 605 return (0); 606 gettimeofday(&tv, NULL); 607 limit = (u_longlong_t)(rquota->rq_bhardlimit) * 608 rquota->rq_bsize / DEV_BSIZE; 609 dqp->dqb_bhardlimit = limit; 610 limit = (u_longlong_t)(rquota->rq_bsoftlimit) * 611 rquota->rq_bsize / DEV_BSIZE; 612 dqp->dqb_bsoftlimit = limit; 613 limit = (u_longlong_t)(rquota->rq_curblocks) * 614 rquota->rq_bsize / DEV_BSIZE; 615 dqp->dqb_curblocks = limit; 616 dqp->dqb_fhardlimit = rquota->rq_fhardlimit; 617 dqp->dqb_fsoftlimit = rquota->rq_fsoftlimit; 618 dqp->dqb_curfiles = rquota->rq_curfiles; 619 dqp->dqb_btimelimit = 620 tv.tv_sec + rquota->rq_btimeleft; 621 dqp->dqb_ftimelimit = 622 tv.tv_sec + rquota->rq_ftimeleft; 623 return (1); 624 } 625 626 case Q_NOQUOTA: 627 break; 628 629 case Q_EPERM: 630 fprintf(stderr, "quota permission error, host: %s\n", hostp); 631 break; 632 633 default: 634 fprintf(stderr, "bad rpc result, host: %s\n", hostp); 635 break; 636 } 637 return (0); 638 } 639 640 int 641 callaurpc(char *host, int prognum, int versnum, int procnum, 642 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) 643 { 644 static enum clnt_stat clnt_stat; 645 struct timeval tottimeout; 646 647 static CLIENT *cl = NULL; 648 static int oldprognum, oldversnum; 649 static char oldhost[MAXHOSTNAMELEN+1]; 650 651 /* 652 * Cache the client handle in case there are lots 653 * of entries in the /etc/mnttab for the same 654 * server. If the server returns an error, don't 655 * make further calls. 656 */ 657 if (cl == NULL || oldprognum != prognum || oldversnum != versnum || 658 strcmp(oldhost, host) != 0) { 659 if (cl) { 660 clnt_destroy(cl); 661 cl = NULL; 662 } 663 cl = clnt_create(host, prognum, versnum, "udp"); 664 if (cl == NULL) 665 return ((int)RPC_TIMEDOUT); 666 if ((cl->cl_auth = authunix_create_default()) == NULL) { 667 clnt_destroy(cl); 668 return (RPC_CANTSEND); 669 } 670 oldprognum = prognum; 671 oldversnum = versnum; 672 (void) strcpy(oldhost, host); 673 clnt_stat = RPC_SUCCESS; 674 } 675 676 if (clnt_stat != RPC_SUCCESS) 677 return ((int)clnt_stat); /* don't bother retrying */ 678 679 tottimeout.tv_sec = 5; 680 tottimeout.tv_usec = 0; 681 clnt_stat = clnt_call(cl, procnum, inproc, in, 682 outproc, out, tottimeout); 683 684 return ((int)clnt_stat); 685 } 686