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