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