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