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