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 * @(#) Copyright (c) 1980, 1990, 1993 The Regents of the University of California. All rights reserved. 37 * @(#)quota.c 8.1 (Berkeley) 6/6/93 38 * $FreeBSD: src/usr.bin/quota/quota.c,v 1.11.2.5 2002/11/30 23:54:21 iedowse Exp $ 39 * $DragonFly: src/usr.bin/quota/quota.c,v 1.4 2004/08/30 18:06:50 eirikn Exp $ 40 */ 41 42 /* 43 * Disk quota reporting program. 44 */ 45 46 #include <sys/param.h> 47 #include <sys/types.h> 48 #include <sys/file.h> 49 #include <sys/stat.h> 50 #include <sys/mount.h> 51 #include <sys/socket.h> 52 53 #include <rpc/rpc.h> 54 #include <rpc/pmap_prot.h> 55 #include <rpcsvc/rquota.h> 56 57 #include <vfs/ufs/quota.h> 58 59 #include <ctype.h> 60 #include <err.h> 61 #include <fstab.h> 62 #include <grp.h> 63 #include <netdb.h> 64 #include <pwd.h> 65 #include <stdio.h> 66 #include <stdlib.h> 67 #include <string.h> 68 #include <unistd.h> 69 70 const char *qfname = QUOTAFILENAME; 71 const char *qfextension[] = INITQFNAMES; 72 73 struct quotause { 74 struct quotause *next; 75 long flags; 76 struct dqblk dqblk; 77 char fsname[MAXPATHLEN + 1]; 78 }; 79 #define FOUND 0x01 80 81 static const char *timeprt(time_t seconds); 82 static struct quotause *getprivs(long id, int quotatype); 83 static void usage(void); 84 static void showuid(u_long uid); 85 static void showgid(u_long gid); 86 static int alldigits(char *s); 87 static void showusrname(char *name); 88 static void showgrpname(char *name); 89 static void showquotas(int type, u_long id, const char *name); 90 static void heading(int type, u_long id, const char *name, const char *tag); 91 static struct quotause *getprivs(long id, int quotatype); 92 static int ufshasquota(struct fstab *fs, int type, char **qfnamep); 93 static int getufsquota(struct fstab *fs, struct quotause *qup, long id, 94 int quotatype); 95 static int getnfsquota(struct statfs *fst, struct quotause *qup, long id, 96 int quotatype); 97 static int callaurpc(char *host, int prognum, int versnum, int procnum, 98 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out); 99 static int alldigits(char *s); 100 101 int lflag; 102 int qflag; 103 int vflag; 104 105 int 106 main(int argc, char *argv[]) 107 { 108 int ngroups; 109 gid_t mygid, gidset[NGROUPS]; 110 int i, gflag = 0, uflag = 0; 111 char ch; 112 113 while ((ch = getopt(argc, argv, "glquv")) != -1) { 114 switch(ch) { 115 case 'g': 116 gflag++; 117 break; 118 case 'l': 119 lflag++; 120 break; 121 case 'q': 122 qflag++; 123 break; 124 case 'u': 125 uflag++; 126 break; 127 case 'v': 128 vflag++; 129 break; 130 default: 131 usage(); 132 } 133 } 134 argc -= optind; 135 argv += optind; 136 if (!uflag && !gflag) 137 uflag++; 138 if (argc == 0) { 139 if (uflag) 140 showuid(getuid()); 141 if (gflag) { 142 mygid = getgid(); 143 ngroups = getgroups(NGROUPS, gidset); 144 if (ngroups < 0) 145 err(1, "getgroups"); 146 showgid(mygid); 147 for (i = 0; i < ngroups; i++) 148 if (gidset[i] != mygid) 149 showgid(gidset[i]); 150 } 151 return(0); 152 } 153 if (uflag && gflag) 154 usage(); 155 if (uflag) { 156 for (; argc > 0; argc--, argv++) { 157 if (alldigits(*argv)) 158 showuid(atoi(*argv)); 159 else 160 showusrname(*argv); 161 } 162 return(0); 163 } 164 if (gflag) { 165 for (; argc > 0; argc--, argv++) { 166 if (alldigits(*argv)) 167 showgid(atoi(*argv)); 168 else 169 showgrpname(*argv); 170 } 171 } 172 return(0); 173 } 174 175 static void 176 usage(void) 177 { 178 179 fprintf(stderr, "%s\n%s\n%s\n", 180 "usage: quota [-glu] [-v | -q]", 181 " quota [-lu] [-v | -q] user ...", 182 " quota -g [-l] [-v | -q] group ..."); 183 exit(1); 184 } 185 186 /* 187 * Print out quotas for a specified user identifier. 188 */ 189 static void 190 showuid(u_long uid) 191 { 192 struct passwd *pwd = getpwuid(uid); 193 u_long myuid; 194 const char *name; 195 196 if (pwd == NULL) 197 name = "(no account)"; 198 else 199 name = pwd->pw_name; 200 myuid = getuid(); 201 if (uid != myuid && myuid != 0) { 202 printf("quota: %s (uid %lu): permission denied\n", name, uid); 203 return; 204 } 205 showquotas(USRQUOTA, uid, name); 206 } 207 208 /* 209 * Print out quotas for a specifed user name. 210 */ 211 static void 212 showusrname(char *name) 213 { 214 struct passwd *pwd = getpwnam(name); 215 u_long myuid; 216 217 if (pwd == NULL) { 218 warnx("%s: unknown user", name); 219 return; 220 } 221 myuid = getuid(); 222 if (pwd->pw_uid != myuid && myuid != 0) { 223 warnx("%s (uid %u): permission denied", name, pwd->pw_uid); 224 return; 225 } 226 showquotas(USRQUOTA, pwd->pw_uid, name); 227 } 228 229 /* 230 * Print out quotas for a specified group identifier. 231 */ 232 static void 233 showgid(u_long gid) 234 { 235 struct group *grp = getgrgid(gid); 236 int ngroups; 237 gid_t mygid, gidset[NGROUPS]; 238 int i; 239 const char *name; 240 241 if (grp == NULL) 242 name = "(no entry)"; 243 else 244 name = grp->gr_name; 245 mygid = getgid(); 246 ngroups = getgroups(NGROUPS, gidset); 247 if (ngroups < 0) { 248 warn("getgroups"); 249 return; 250 } 251 if (gid != mygid) { 252 for (i = 0; i < ngroups; i++) 253 if (gid == gidset[i]) 254 break; 255 if (i >= ngroups && getuid() != 0) { 256 warnx("%s (gid %lu): permission denied", name, gid); 257 return; 258 } 259 } 260 showquotas(GRPQUOTA, gid, name); 261 } 262 263 /* 264 * Print out quotas for a specifed group name. 265 */ 266 static void 267 showgrpname(char *name) 268 { 269 struct group *grp = getgrnam(name); 270 int ngroups; 271 gid_t mygid, gidset[NGROUPS]; 272 int i; 273 274 if (grp == NULL) { 275 warnx("%s: unknown group", name); 276 return; 277 } 278 mygid = getgid(); 279 ngroups = getgroups(NGROUPS, gidset); 280 if (ngroups < 0) { 281 warn("getgroups"); 282 return; 283 } 284 if (grp->gr_gid != mygid) { 285 for (i = 0; i < ngroups; i++) 286 if (grp->gr_gid == gidset[i]) 287 break; 288 if (i >= ngroups && getuid() != 0) { 289 warnx("%s (gid %u): permission denied", name, 290 grp->gr_gid); 291 return; 292 } 293 } 294 showquotas(GRPQUOTA, grp->gr_gid, name); 295 } 296 297 static void 298 showquotas(int type, u_long id, const char *name) 299 { 300 struct quotause *qup; 301 struct quotause *quplist; 302 const char *msgi, *msgb; 303 const char *nam; 304 int lines = 0; 305 static time_t now; 306 307 if (now == 0) 308 time(&now); 309 quplist = getprivs(id, type); 310 for (qup = quplist; qup; qup = qup->next) { 311 if (!vflag && 312 qup->dqblk.dqb_isoftlimit == 0 && 313 qup->dqblk.dqb_ihardlimit == 0 && 314 qup->dqblk.dqb_bsoftlimit == 0 && 315 qup->dqblk.dqb_bhardlimit == 0) 316 continue; 317 msgi = (char *)0; 318 if (qup->dqblk.dqb_ihardlimit && 319 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_ihardlimit) 320 msgi = "File limit reached on"; 321 else if (qup->dqblk.dqb_isoftlimit && 322 qup->dqblk.dqb_curinodes >= qup->dqblk.dqb_isoftlimit) { 323 if (qup->dqblk.dqb_itime > now) 324 msgi = "In file grace period on"; 325 else 326 msgi = "Over file quota on"; 327 } 328 msgb = (char *)0; 329 if (qup->dqblk.dqb_bhardlimit && 330 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bhardlimit) 331 msgb = "Block limit reached on"; 332 else if (qup->dqblk.dqb_bsoftlimit && 333 qup->dqblk.dqb_curblocks >= qup->dqblk.dqb_bsoftlimit) { 334 if (qup->dqblk.dqb_btime > now) 335 msgb = "In block grace period on"; 336 else 337 msgb = "Over block quota on"; 338 } 339 if (qflag) { 340 if ((msgi != (char *)0 || msgb != (char *)0) && 341 lines++ == 0) 342 heading(type, id, name, ""); 343 if (msgi != (char *)0) 344 printf("\t%s %s\n", msgi, qup->fsname); 345 if (msgb != (char *)0) 346 printf("\t%s %s\n", msgb, qup->fsname); 347 continue; 348 } 349 if (vflag || 350 qup->dqblk.dqb_curblocks || 351 qup->dqblk.dqb_curinodes) { 352 if (lines++ == 0) 353 heading(type, id, name, ""); 354 nam = qup->fsname; 355 if (strlen(qup->fsname) > 15) { 356 printf("%s\n", qup->fsname); 357 nam = ""; 358 } 359 printf("%15s%8lu%c%7lu%8lu%8s" 360 , nam 361 , (u_long) (dbtob(qup->dqblk.dqb_curblocks) 362 / 1024) 363 , (msgb == (char *)0) ? ' ' : '*' 364 , (u_long) (dbtob(qup->dqblk.dqb_bsoftlimit) 365 / 1024) 366 , (u_long) (dbtob(qup->dqblk.dqb_bhardlimit) 367 / 1024) 368 , (msgb == (char *)0) ? "" 369 :timeprt(qup->dqblk.dqb_btime)); 370 printf("%8lu%c%7lu%8lu%8s\n" 371 , (u_long)qup->dqblk.dqb_curinodes 372 , (msgi == (char *)0) ? ' ' : '*' 373 , (u_long)qup->dqblk.dqb_isoftlimit 374 , (u_long)qup->dqblk.dqb_ihardlimit 375 , (msgi == (char *)0) ? "" 376 : timeprt(qup->dqblk.dqb_itime) 377 ); 378 continue; 379 } 380 } 381 if (!qflag && lines == 0) 382 heading(type, id, name, "none"); 383 } 384 385 static void 386 heading(int type, u_long id, const char *name, const char *tag) 387 { 388 389 printf("Disk quotas for %s %s (%cid %lu): %s\n", qfextension[type], 390 name, *qfextension[type], id, tag); 391 if (!qflag && tag[0] == '\0') { 392 printf("%15s%8s %7s%8s%8s%8s %7s%8s%8s\n" 393 , "Filesystem" 394 , "usage" 395 , "quota" 396 , "limit" 397 , "grace" 398 , "files" 399 , "quota" 400 , "limit" 401 , "grace" 402 ); 403 } 404 } 405 406 /* 407 * Calculate the grace period and return a printable string for it. 408 */ 409 static const char * 410 timeprt(time_t seconds) 411 { 412 time_t hours, minutes; 413 static char buf[20]; 414 static time_t now; 415 416 if (now == 0) 417 time(&now); 418 if (now > seconds) 419 return ("none"); 420 seconds -= now; 421 minutes = (seconds + 30) / 60; 422 hours = (minutes + 30) / 60; 423 if (hours >= 36) { 424 sprintf(buf, "%lddays", ((long)hours + 12) / 24); 425 return (buf); 426 } 427 if (minutes >= 60) { 428 sprintf(buf, "%2ld:%ld", (long)minutes / 60, 429 (long)minutes % 60); 430 return (buf); 431 } 432 sprintf(buf, "%2ld", (long)minutes); 433 return (buf); 434 } 435 436 /* 437 * Collect the requested quota information. 438 */ 439 static struct quotause * 440 getprivs(long id, int quotatype) 441 { 442 struct quotause *qup, *quptail = NULL; 443 struct fstab *fs; 444 struct quotause *quphead; 445 struct statfs *fst; 446 int nfst, i; 447 448 qup = quphead = (struct quotause *)0; 449 450 nfst = getmntinfo(&fst, MNT_NOWAIT); 451 if (nfst == 0) 452 errx(2, "no filesystems mounted!"); 453 setfsent(); 454 for (i=0; i<nfst; i++) { 455 if (qup == NULL) { 456 if ((qup = (struct quotause *)malloc(sizeof *qup)) 457 == NULL) 458 errx(2, "out of memory"); 459 } 460 if (strcmp(fst[i].f_fstypename, "nfs") == 0) { 461 if (lflag) 462 continue; 463 if (getnfsquota(&fst[i], qup, id, quotatype) 464 == 0) 465 continue; 466 } else if (strcmp(fst[i].f_fstypename, "ufs") == 0) { 467 /* 468 * XXX 469 * UFS filesystems must be in /etc/fstab, and must 470 * indicate that they have quotas on (?!) This is quite 471 * unlike SunOS where quotas can be enabled/disabled 472 * on a filesystem independent of /etc/fstab, and it 473 * will still print quotas for them. 474 */ 475 if ((fs = getfsspec(fst[i].f_mntfromname)) == NULL) 476 continue; 477 if (getufsquota(fs, qup, id, quotatype) == 0) 478 continue; 479 } else 480 continue; 481 strcpy(qup->fsname, fst[i].f_mntonname); 482 if (quphead == NULL) 483 quphead = qup; 484 else 485 quptail->next = qup; 486 quptail = qup; 487 quptail->next = 0; 488 qup = NULL; 489 } 490 if (qup) 491 free(qup); 492 endfsent(); 493 return (quphead); 494 } 495 496 /* 497 * Check to see if a particular quota is to be enabled. 498 */ 499 static int 500 ufshasquota(struct fstab *fs, int type, char **qfnamep) 501 { 502 static char initname, usrname[100], grpname[100]; 503 static char buf[BUFSIZ]; 504 char *opt, *cp; 505 506 if (!initname) { 507 sprintf(usrname, "%s%s", qfextension[USRQUOTA], qfname); 508 sprintf(grpname, "%s%s", qfextension[GRPQUOTA], qfname); 509 initname = 1; 510 } 511 strcpy(buf, fs->fs_mntops); 512 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 513 if ((cp = strchr(opt, '='))) 514 *cp++ = '\0'; 515 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 516 break; 517 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 518 break; 519 } 520 if (!opt) 521 return (0); 522 if (cp) { 523 *qfnamep = cp; 524 return (1); 525 } 526 (void) sprintf(buf, "%s/%s.%s", fs->fs_file, qfname, qfextension[type]); 527 *qfnamep = buf; 528 return (1); 529 } 530 531 static int 532 getufsquota(struct fstab *fs, struct quotause *qup, long id, 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, (char *)&qup->dqblk) != 0) { 542 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 543 warn("%s", 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 warn("read error: %s", qfpathname); 559 close(fd); 560 return (0); 561 } 562 close(fd); 563 } 564 return (1); 565 } 566 567 static int 568 getnfsquota(struct statfs *fst, struct quotause *qup, long id, int quotatype) 569 { 570 struct getquota_args gq_args; 571 struct getquota_rslt gq_rslt; 572 struct dqblk *dqp = &qup->dqblk; 573 struct timeval tv; 574 char *cp; 575 576 if (fst->f_flags & MNT_LOCAL) 577 return (0); 578 579 /* 580 * rpc.rquotad does not support group quotas 581 */ 582 if (quotatype != USRQUOTA) 583 return (0); 584 585 /* 586 * must be some form of "hostname:/path" 587 */ 588 cp = strchr(fst->f_mntfromname, ':'); 589 if (cp == NULL) { 590 warnx("cannot find hostname for %s", fst->f_mntfromname); 591 return (0); 592 } 593 594 *cp = '\0'; 595 if (*(cp+1) != '/') { 596 *cp = ':'; 597 return (0); 598 } 599 600 /* Avoid attempting the RPC for special amd(8) filesystems. */ 601 if (strncmp(fst->f_mntfromname, "pid", 3) == 0 && 602 strchr(fst->f_mntfromname, '@') != NULL) { 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, (xdrproc_t)xdr_getquota_args, (char *)&gq_args, 611 (xdrproc_t)xdr_getquota_rslt, (char *)&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 warnx("quota permission error, host: %s", 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 warnx("bad rpc result, host: %s", fst->f_mntfromname); 651 break; 652 } 653 *cp = ':'; 654 return (0); 655 } 656 657 static int 658 callaurpc(char *host, int prognum, int versnum, int procnum, 659 xdrproc_t inproc, char *in, xdrproc_t outproc, char *out) 660 { 661 struct sockaddr_in server_addr; 662 enum clnt_stat clnt_stat; 663 struct hostent *hp; 664 struct timeval timeout, tottimeout; 665 666 CLIENT *client = NULL; 667 int sock = RPC_ANYSOCK; 668 669 if ((hp = gethostbyname(host)) == NULL) 670 return ((int) RPC_UNKNOWNHOST); 671 timeout.tv_usec = 0; 672 timeout.tv_sec = 6; 673 bcopy(hp->h_addr, &server_addr.sin_addr, 674 MIN(hp->h_length,(int)sizeof(server_addr.sin_addr))); 675 server_addr.sin_family = AF_INET; 676 server_addr.sin_port = 0; 677 678 if ((client = clntudp_create(&server_addr, prognum, 679 versnum, timeout, &sock)) == NULL) 680 return ((int) rpc_createerr.cf_stat); 681 682 client->cl_auth = authunix_create_default(); 683 tottimeout.tv_sec = 25; 684 tottimeout.tv_usec = 0; 685 clnt_stat = clnt_call(client, procnum, inproc, in, 686 outproc, out, tottimeout); 687 688 return ((int) clnt_stat); 689 } 690 691 static int 692 alldigits(char *s) 693 { 694 int c; 695 696 c = *s++; 697 do { 698 if (!isdigit(c)) 699 return (0); 700 } while ((c = *s++)); 701 return (1); 702 } 703