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