1/* $NetBSD$ */ 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. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35#include <sys/cdefs.h> 36#ifndef lint 37__COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\ 38 The Regents of the University of California. All rights reserved."); 39#endif /* not lint */ 40 41#ifndef lint 42#if 0 43static char sccsid[] = "@(#)quota.c 8.4 (Berkeley) 4/28/95"; 44#else 45__RCSID("$NetBSD$"); 46#endif 47#endif /* not lint */ 48 49/* 50 * Disk quota reporting program. 51 */ 52#include <sys/param.h> 53#include <sys/types.h> 54#include <sys/file.h> 55#include <sys/stat.h> 56#include <sys/mount.h> 57#include <sys/socket.h> 58 59#include <assert.h> 60#include <ctype.h> 61#include <err.h> 62#include <errno.h> 63#include <fstab.h> 64#include <grp.h> 65#include <netdb.h> 66#include <pwd.h> 67#include <stdio.h> 68#include <stdlib.h> 69#include <string.h> 70#include <time.h> 71#include <unistd.h> 72 73#include <quota.h> 74 75#include "printquota.h" 76 77struct quotause { 78 struct quotause *next; 79 uid_t id; 80 struct quotaval *qvs; 81 unsigned numqvs; 82 char fsname[MAXPATHLEN + 1]; 83 struct quotahandle *qh; 84}; 85 86static int anyusage(struct quotaval *, unsigned); 87static int anyover(struct quotaval *, unsigned, time_t); 88static const char *getovermsg(struct quotaval *, const char *, time_t); 89static struct quotause *getprivs(id_t, int); 90static void heading(int, const char *, id_t, const char *, const char *); 91static int isover(struct quotaval *qv, time_t now); 92static void printqv(struct quotaval *, int, time_t); 93static void showgid(gid_t); 94static void showgrpname(const char *); 95static void showonequota(int, const char *, id_t, const char *, 96 struct quotause *); 97static void showquotas(int, const char *, id_t, const char *); 98static void showuid(uid_t); 99static void showusrname(const char *); 100static int unlimited(struct quotaval *qvs, unsigned numqvs); 101static void usage(void) __dead; 102 103static int qflag = 0; 104static int vflag = 0; 105static int hflag = 0; 106static int dflag = 0; 107static uid_t myuid; 108static int needheading; 109 110int 111main(int argc, char *argv[]) 112{ 113 int ngroups; 114 gid_t mygid, gidset[NGROUPS]; 115 int i, gflag = 0, uflag = 0; 116 int ch; 117 118 myuid = getuid(); 119 while ((ch = getopt(argc, argv, "dhugvq")) != -1) { 120 switch(ch) { 121 case 'g': 122 gflag++; 123 break; 124 case 'u': 125 uflag++; 126 break; 127 case 'v': 128 vflag++; 129 break; 130 case 'q': 131 qflag++; 132 break; 133 case 'h': 134 hflag++; 135 break; 136 case 'd': 137 dflag++; 138 break; 139 default: 140 usage(); 141 } 142 } 143 argc -= optind; 144 argv += optind; 145 if (!uflag && !gflag) 146 uflag++; 147 if (dflag) { 148#if 0 149 if (myuid != 0) 150 errx(1, "-d: permission denied"); 151#endif 152 if (uflag) 153 showquotas(QUOTA_IDTYPE_USER, "user", 0, ""); 154 if (gflag) 155 showquotas(QUOTA_IDTYPE_GROUP, "group", 0, ""); 156 return 0; 157 } 158 if (argc == 0) { 159 if (uflag) 160 showuid(myuid); 161 if (gflag) { 162 if (dflag) 163 showgid(0); 164 else { 165 mygid = getgid(); 166 ngroups = getgroups(NGROUPS, gidset); 167 if (ngroups < 0) 168 err(1, "getgroups"); 169 showgid(mygid); 170 for (i = 0; i < ngroups; i++) 171 if (gidset[i] != mygid) 172 showgid(gidset[i]); 173 } 174 } 175 return 0; 176 } 177 if (uflag && gflag) 178 usage(); 179 if (uflag) { 180 for (; argc > 0; argc--, argv++) { 181 if (alldigits(*argv)) 182 showuid((uid_t)atoi(*argv)); 183 else 184 showusrname(*argv); 185 } 186 return 0; 187 } 188 if (gflag) { 189 for (; argc > 0; argc--, argv++) { 190 if (alldigits(*argv)) 191 showgid((gid_t)atoi(*argv)); 192 else 193 showgrpname(*argv); 194 } 195 return 0; 196 } 197 /* NOTREACHED */ 198 return 0; 199} 200 201static void 202usage(void) 203{ 204 const char *p = getprogname(); 205 fprintf(stderr, "Usage: %s [-Dhguqv]\n" 206 "\t%s [-Dhqv] -u username ...\n" 207 "\t%s [-Dhqv] -g groupname ...\n" 208 "\t%s -d [-Dhguqv]\n", p, p, p, p); 209 exit(1); 210} 211 212/* 213 * Print out quotas for a specified user identifier. 214 */ 215static void 216showuid(uid_t uid) 217{ 218 struct passwd *pwd = getpwuid(uid); 219 const char *name; 220 221 if (pwd == NULL) 222 name = "(no account)"; 223 else 224 name = pwd->pw_name; 225 if (uid != myuid && myuid != 0) { 226 warnx("%s (uid %d): permission denied", name, uid); 227 return; 228 } 229 showquotas(QUOTA_IDTYPE_USER, "user", uid, name); 230} 231 232/* 233 * Print out quotas for a specified user name. 234 */ 235static void 236showusrname(const char *name) 237{ 238 struct passwd *pwd = getpwnam(name); 239 240 if (pwd == NULL) { 241 warnx("%s: unknown user", name); 242 return; 243 } 244 if (pwd->pw_uid != myuid && myuid != 0) { 245 warnx("%s (uid %d): permission denied", name, pwd->pw_uid); 246 return; 247 } 248 showquotas(QUOTA_IDTYPE_USER, "user", pwd->pw_uid, name); 249} 250 251/* 252 * Print out quotas for a specified group identifier. 253 */ 254static void 255showgid(gid_t gid) 256{ 257 struct group *grp = getgrgid(gid); 258 int ngroups; 259 gid_t mygid, gidset[NGROUPS]; 260 int i; 261 const char *name; 262 263 if (grp == NULL) 264 name = "(no entry)"; 265 else 266 name = grp->gr_name; 267 mygid = getgid(); 268 ngroups = getgroups(NGROUPS, gidset); 269 if (ngroups < 0) { 270 warn("getgroups"); 271 return; 272 } 273 if (gid != mygid) { 274 for (i = 0; i < ngroups; i++) 275 if (gid == gidset[i]) 276 break; 277 if (i >= ngroups && myuid != 0) { 278 warnx("%s (gid %d): permission denied", name, gid); 279 return; 280 } 281 } 282 showquotas(QUOTA_IDTYPE_GROUP, "group", gid, name); 283} 284 285/* 286 * Print out quotas for a specified group name. 287 */ 288static void 289showgrpname(const char *name) 290{ 291 struct group *grp = getgrnam(name); 292 int ngroups; 293 gid_t mygid, gidset[NGROUPS]; 294 int i; 295 296 if (grp == NULL) { 297 warnx("%s: unknown group", name); 298 return; 299 } 300 mygid = getgid(); 301 ngroups = getgroups(NGROUPS, gidset); 302 if (ngroups < 0) { 303 warn("getgroups"); 304 return; 305 } 306 if (grp->gr_gid != mygid) { 307 for (i = 0; i < ngroups; i++) 308 if (grp->gr_gid == gidset[i]) 309 break; 310 if (i >= ngroups && myuid != 0) { 311 warnx("%s (gid %d): permission denied", 312 name, grp->gr_gid); 313 return; 314 } 315 } 316 showquotas(QUOTA_IDTYPE_GROUP, "group", grp->gr_gid, name); 317} 318 319static void 320showquotas(int idtype, const char *idtypename, id_t id, const char *idname) 321{ 322 struct quotause *qup; 323 struct quotause *quplist; 324 325 needheading = 1; 326 327 quplist = getprivs(id, idtype); 328 for (qup = quplist; qup; qup = qup->next) { 329 showonequota(idtype, idtypename, id, idname, qup); 330 } 331 if (!qflag) { 332 /* In case nothing printed, issue a header saying "none" */ 333 heading(idtype, idtypename, id, idname, "none"); 334 } 335} 336 337static void 338showonequota(int idtype, const char *idtypename, id_t id, const char *idname, 339 struct quotause *qup) 340{ 341 static time_t now; 342 struct quotaval *qvs; 343 unsigned numqvs, i; 344 const char *msg; 345 346 qvs = qup->qvs; 347 numqvs = qup->numqvs; 348 349 if (now == 0) { 350 time(&now); 351 } 352 353 if (!vflag && unlimited(qvs, numqvs)) { 354 return; 355 } 356 357 if (qflag) { 358 for (i=0; i<numqvs; i++) { 359 msg = getovermsg(&qvs[i], 360 quota_idtype_getname(qup->qh, i), 361 now); 362 if (msg != NULL) { 363 heading(idtype, idtypename, id, idname, ""); 364 printf("\t%s %s\n", msg, qup->fsname); 365 } 366 } 367 return; 368 } 369 370 /* 371 * XXX this behavior appears to be demanded by the ATF tests, 372 * although it seems to be at variance with the preexisting 373 * logic in quota.c. 374 */ 375 if (unlimited(qvs, numqvs) && !anyusage(qvs, numqvs)) { 376 return; 377 } 378 379 /* 380 * XXX: anyover can in fact be true if anyusage is not true, 381 * if there's a quota of zero set on some volume. This is 382 * because the check we do checks if adding one more thing 383 * will go over. That is reasonable, I suppose, but arguably 384 * the resulting behavior with usage 0 is a bug. (Also, what 385 * reason do we have to believe that the reported grace expire 386 * time is valid if we aren't in fact over yet?) 387 */ 388 389 if (vflag || dflag || anyover(qvs, numqvs, now) || 390 anyusage(qvs, numqvs)) { 391 heading(idtype, idtypename, id, idname, ""); 392 if (strlen(qup->fsname) > 4) { 393 printf("%s\n", qup->fsname); 394 printf("%12s", ""); 395 } else { 396 printf("%12s", qup->fsname); 397 } 398 399 for (i=0; i<numqvs; i++) { 400 printqv(&qvs[i], 401 quota_objtype_isbytes(qup->qh, i), now); 402 } 403 printf("\n"); 404 } 405} 406 407static void 408heading(int idtype, const char *idtypename, id_t id, const char *idname, 409 const char *tag) 410{ 411 if (needheading == 0) 412 return; 413 needheading = 0; 414 415 if (dflag) 416 printf("Default %s disk quotas: %s\n", idtypename, tag); 417 else 418 printf("Disk quotas for %s %s (%cid %u): %s\n", 419 idtypename, idname, idtypename[0], id, tag); 420 421 if (!qflag && tag[0] == '\0') { 422 printf("%12s%9s %8s%9s%8s%8s %7s%8s%8s\n" 423 , "Filesystem" 424 , "blocks" 425 , "quota" 426 , "limit" 427 , "grace" 428 , "files" 429 , "quota" 430 , "limit" 431 , "grace" 432 ); 433 } 434} 435 436static void 437printqv(struct quotaval *qv, int isbytes, time_t now) 438{ 439 char buf[20]; 440 const char *str; 441 int intprtflags, over, width; 442 443 /* 444 * The assorted finagling of width is to match the previous 445 * open-coded formatting for exactly two quota object types, 446 * which was chosen to make the default report fit in 80 447 * columns. 448 */ 449 450 width = isbytes ? 9 : 8; 451 intprtflags = isbytes ? HN_B : 0; 452 over = isover(qv, now); 453 454 str = intprt(buf, width, qv->qv_usage, intprtflags, hflag); 455 printf("%*s", width, str); 456 457 printf("%c", over ? '*' : ' '); 458 459 str = intprt(buf, width, qv->qv_softlimit, intprtflags, hflag); 460 printf("%*s", width-1, str); 461 462 str = intprt(buf, width, qv->qv_hardlimit, intprtflags, hflag); 463 printf("%*s", width, str); 464 465 if (over) { 466 str = timeprt(buf, 9, now, qv->qv_expiretime); 467 } else if (vflag && qv->qv_grace != QUOTA_NOTIME) { 468 str = timeprt(buf, 9, 0, qv->qv_grace); 469 } else { 470 str = ""; 471 } 472 printf("%8s", str); 473} 474 475/* 476 * Collect the requested quota information. 477 */ 478static struct quotause * 479getprivs(id_t id, int idtype) 480{ 481 struct quotause *qup, *quptail; 482 struct quotause *quphead; 483 struct statvfs *fst; 484 struct quotakey qk; 485 int nfst, i; 486 unsigned j; 487 488 qup = quphead = quptail = NULL; 489 490 nfst = getmntinfo(&fst, MNT_WAIT); 491 if (nfst == 0) 492 errx(2, "no filesystems mounted!"); 493 for (i = 0; i < nfst; i++) { 494 if (qup == NULL) { 495 if ((qup = malloc(sizeof *qup)) == NULL) 496 err(1, "Out of memory"); 497 } 498 qup->qh = quota_open(fst[i].f_mntonname); 499 if (qup->qh == NULL) { 500 if (errno == EOPNOTSUPP || errno == ENXIO) { 501 continue; 502 } 503 err(1, "%s: quota_open", fst[i].f_mntonname); 504 } 505#if 0 506 if (strncmp(fst[i].f_fstypename, "nfs", 507 sizeof(fst[i].f_fstypename)) == 0) { 508 version = 0; 509 qup->numqvs = QUOTA_NLIMITS; 510 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0])); 511 if (qup->qvs == NULL) { 512 err(1, "Out of memory"); 513 } 514 if (getnfsquota(fst[i].f_mntfromname, 515 qup->qvs, id, ufs_quota_class_names[idtype]) != 1) 516 continue; 517 } else if ((fst[i].f_flag & ST_QUOTA) != 0) { 518 qup->numqvs = QUOTA_NLIMITS; 519 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0])); 520 if (qup->qvs == NULL) { 521 err(1, "Out of memory"); 522 } 523 if (getvfsquota(fst[i].f_mntonname, qup->qvs, &version, 524 id, idtype, dflag, 0) != 1) 525 continue; 526 } else 527 continue; 528#else 529 qup->numqvs = quota_getnumidtypes(qup->qh); 530 qup->qvs = malloc(qup->numqvs * sizeof(qup->qvs[0])); 531 if (qup->qvs == NULL) { 532 err(1, "Out of memory"); 533 } 534 qk.qk_idtype = idtype; 535 if (dflag) { 536 qk.qk_id = QUOTA_DEFAULTID; 537 } else { 538 qk.qk_id = id; 539 } 540 for (j=0; j<qup->numqvs; j++) { 541 qk.qk_objtype = j; 542 if (quota_get(qup->qh, &qk, &qup->qvs[j]) < 0) { 543 if (errno != ENOENT && errno != ENODEV) { 544 warn("%s: quota_get (objtype %u)", 545 fst[i].f_mntonname, j); 546 } 547 quotaval_clear(&qup->qvs[j]); 548 } 549 } 550#endif 551 (void)strlcpy(qup->fsname, fst[i].f_mntonname, 552 sizeof(qup->fsname)); 553 if (quphead == NULL) 554 quphead = qup; 555 else 556 quptail->next = qup; 557 quptail = qup; 558 quptail->next = 0; 559 qup = NULL; 560 } 561 free(qup); 562 return quphead; 563} 564 565static int 566unlimited(struct quotaval *qvs, unsigned numqvs) 567{ 568 unsigned i; 569 570 for (i=0; i<numqvs; i++) { 571 if (qvs[i].qv_softlimit != QUOTA_NOLIMIT || 572 qvs[i].qv_hardlimit != QUOTA_NOLIMIT) { 573 return 0; 574 } 575 } 576 return 1; 577} 578 579static int 580anyusage(struct quotaval *qvs, unsigned numqvs) 581{ 582 unsigned i; 583 584 for (i=0; i<numqvs; i++) { 585 if (qvs[i].qv_usage > 0) { 586 return 1; 587 } 588 } 589 return 0; 590} 591 592static int 593anyover(struct quotaval *qvs, unsigned numqvs, time_t now) 594{ 595 unsigned i; 596 597 for (i=0; i<numqvs; i++) { 598 if (isover(&qvs[i], now)) { 599 return 1; 600 } 601 } 602 return 0; 603} 604 605static int 606isover(struct quotaval *qv, time_t now) 607{ 608 return (qv->qv_usage >= qv->qv_hardlimit || 609 qv->qv_usage >= qv->qv_softlimit); 610} 611 612static const char * 613getovermsg(struct quotaval *qv, const char *what, time_t now) 614{ 615 static char buf[64]; 616 617 if (qv->qv_usage >= qv->qv_hardlimit) { 618 snprintf(buf, sizeof(buf), "%c%s limit reached on", 619 toupper((unsigned char)what[0]), what+1); 620 return buf; 621 } 622 623 if (qv->qv_usage < qv->qv_softlimit) { 624 /* Ok */ 625 return NULL; 626 } 627 628 if (now > qv->qv_expiretime) { 629 snprintf(buf, sizeof(buf), "Over %s quota on", what); 630 return buf; 631 } 632 633 snprintf(buf, sizeof(buf), "In %s grace period on", what); 634 return buf; 635} 636