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