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