1 /* $NetBSD: repquota.c,v 1.43 2012/02/13 01:35:09 dholland Exp $ */ 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 43 static char sccsid[] = "@(#)repquota.c 8.2 (Berkeley) 11/22/94"; 44 #else 45 __RCSID("$NetBSD: repquota.c,v 1.43 2012/02/13 01:35:09 dholland Exp $"); 46 #endif 47 #endif /* not lint */ 48 49 /* 50 * Quota report 51 */ 52 #include <sys/param.h> 53 #include <sys/stat.h> 54 #include <sys/types.h> 55 #include <sys/statvfs.h> 56 57 #include <errno.h> 58 #include <err.h> 59 #include <fstab.h> 60 #include <grp.h> 61 #include <pwd.h> 62 #include <stdio.h> 63 #include <stdlib.h> 64 #include <string.h> 65 #include <unistd.h> 66 67 #include <quota.h> 68 69 #include "printquota.h" 70 71 /* 72 * XXX. Ideally we shouldn't compile either of these in, but it's a 73 * nontrivial rework to avoid it and it'll work ok for now. 74 */ 75 #define REPQUOTA_NUMIDTYPES 2 76 #define REPQUOTA_NUMOBJTYPES 2 77 78 struct fileusage { 79 struct fileusage *fu_next; 80 struct quotaval fu_qv[REPQUOTA_NUMOBJTYPES]; 81 uint32_t fu_id; 82 char fu_name[1]; 83 /* actually bigger */ 84 }; 85 86 #define FUHASH 1024 /* must be power of two */ 87 static struct fileusage *fuhead[REPQUOTA_NUMIDTYPES][FUHASH]; 88 89 /* highest addid()'ed identifier per idtype */ 90 static uint32_t highid[REPQUOTA_NUMIDTYPES]; 91 92 int valid[REPQUOTA_NUMIDTYPES]; 93 94 static struct quotaval defaultqv[REPQUOTA_NUMIDTYPES][REPQUOTA_NUMOBJTYPES]; 95 96 static int vflag = 0; /* verbose */ 97 static int aflag = 0; /* all file systems */ 98 static int Dflag = 0; /* debug */ 99 static int hflag = 0; /* humanize */ 100 static int xflag = 0; /* export */ 101 102 /* 103 * XXX this should go away and be replaced with a call to 104 * quota_idtype_getname(), but that needs a quotahandle and requires 105 * the same nontrivial rework as getting rid of REPQUOTA_NUMIDTYPES. 106 */ 107 static const char *const repquota_idtype_names[REPQUOTA_NUMIDTYPES] = { 108 "user", 109 "group", 110 }; 111 112 static struct fileusage *addid(uint32_t, int, const char *); 113 static struct fileusage *lookup(uint32_t, int); 114 static struct fileusage *qremove(uint32_t, int); 115 static int repquota(struct quotahandle *, int); 116 static void usage(void) __attribute__((__noreturn__)); 117 static void printquotas(int, struct quotahandle *); 118 static void exportquotas(void); 119 static int oneof(const char *, char *[], int cnt); 120 static int isover(struct quotaval *qv, time_t now); 121 122 int 123 main(int argc, char **argv) 124 { 125 int gflag = 0, uflag = 0, errs = 0; 126 long i, argnum, done = 0; 127 int ch; 128 struct statvfs *fst; 129 int nfst; 130 struct quotahandle *qh; 131 132 if (!strcmp(getprogname(), "quotadump")) { 133 xflag = 1; 134 } 135 136 while ((ch = getopt(argc, argv, "Daguhvx")) != -1) { 137 switch(ch) { 138 case 'a': 139 aflag++; 140 break; 141 case 'g': 142 gflag++; 143 break; 144 case 'u': 145 uflag++; 146 break; 147 case 'h': 148 hflag++; 149 break; 150 case 'v': 151 vflag++; 152 break; 153 case 'D': 154 Dflag++; 155 break; 156 case 'x': 157 xflag++; 158 break; 159 default: 160 usage(); 161 } 162 } 163 argc -= optind; 164 argv += optind; 165 if (xflag && (argc != 1 || aflag)) 166 usage(); 167 if (argc == 0 && !aflag) 168 usage(); 169 if (!gflag && !uflag) { 170 if (aflag) 171 gflag++; 172 uflag++; 173 } 174 175 nfst = getmntinfo(&fst, MNT_WAIT); 176 if (nfst == 0) 177 errx(1, "no filesystems mounted!"); 178 for (i = 0; i < nfst; i++) { 179 if ((fst[i].f_flag & ST_QUOTA) == 0) 180 continue; 181 /* check if we want this volume */ 182 if (!aflag) { 183 argnum = oneof(fst[i].f_mntonname, argv, argc); 184 if (argnum < 0) { 185 argnum = oneof(fst[i].f_mntfromname, 186 argv, argc); 187 } 188 if (argnum < 0) { 189 continue; 190 } 191 done |= 1U << argnum; 192 } 193 194 qh = quota_open(fst[i].f_mntonname); 195 if (qh == NULL) { 196 /* XXX: check this errno */ 197 if (errno == EOPNOTSUPP || errno == ENXIO) { 198 continue; 199 } 200 warn("%s: quota_open", fst[i].f_mntonname); 201 continue; 202 } 203 204 if (gflag) 205 errs += repquota(qh, QUOTA_IDTYPE_GROUP); 206 if (uflag) 207 errs += repquota(qh, QUOTA_IDTYPE_USER); 208 209 quota_close(qh); 210 } 211 if (xflag) 212 exportquotas(); 213 for (i = 0; i < argc; i++) 214 if ((done & (1U << i)) == 0) 215 warnx("%s not mounted", argv[i]); 216 return errs; 217 } 218 219 static void 220 usage(void) 221 { 222 const char *p = getprogname(); 223 fprintf(stderr, "usage: %s [-D] [-v] [-g] [-u] -a\n" 224 "\t%s [-D] [-v] [-g] [-u] filesys ...\n" 225 "\t%s -x [-D] [-g] [-u] filesys\n", p, p, p); 226 exit(1); 227 } 228 229 static int 230 repquota(struct quotahandle *qh, int idtype) 231 { 232 struct quotacursor *qc; 233 struct quotakey qk; 234 struct quotaval qv; 235 struct quotaval *qvp; 236 struct fileusage *fup; 237 238 qc = quota_opencursor(qh); 239 if (qc == NULL) { 240 return 1; 241 } 242 243 if (idtype == QUOTA_IDTYPE_USER) { 244 quotacursor_skipidtype(qc, QUOTA_IDTYPE_GROUP); 245 } 246 if (idtype == QUOTA_IDTYPE_GROUP) { 247 quotacursor_skipidtype(qc, QUOTA_IDTYPE_USER); 248 } 249 250 valid[idtype] = 0; 251 while (!quotacursor_atend(qc)) { 252 if (quotacursor_get(qc, &qk, &qv)) { 253 err(1, "%s: quotacursor_get", quota_getmountpoint(qh)); 254 } 255 if (qk.qk_idtype != idtype) { 256 continue; 257 } 258 259 valid[idtype] = 1; 260 if (qk.qk_id == QUOTA_DEFAULTID) { 261 qvp = defaultqv[idtype]; 262 } else { 263 if ((fup = lookup(qk.qk_id, idtype)) == 0) 264 fup = addid(qk.qk_id, idtype, (char *)0); 265 qvp = fup->fu_qv; 266 } 267 if (qk.qk_objtype == QUOTA_OBJTYPE_BLOCKS) { 268 qvp[QUOTA_OBJTYPE_BLOCKS] = qv; 269 } else if (qk.qk_objtype == QUOTA_OBJTYPE_FILES) { 270 qvp[QUOTA_OBJTYPE_FILES] = qv; 271 } 272 } 273 274 if (xflag == 0 && valid[idtype]) 275 printquotas(idtype, qh); 276 277 return 0; 278 } 279 280 static void 281 printquotas(int idtype, struct quotahandle *qh) 282 { 283 static int multiple = 0; 284 uint32_t id; 285 int i; 286 struct fileusage *fup; 287 struct quotaval *q; 288 const char *timemsg[REPQUOTA_NUMOBJTYPES]; 289 char overchar[REPQUOTA_NUMOBJTYPES]; 290 time_t now; 291 char b0[2][20], b1[20], b2[20], b3[20]; 292 int ok, objtype; 293 int isbytes, width; 294 295 switch (idtype) { 296 case QUOTA_IDTYPE_GROUP: 297 { 298 struct group *gr; 299 setgrent(); 300 while ((gr = getgrent()) != 0) 301 (void)addid(gr->gr_gid, idtype, gr->gr_name); 302 endgrent(); 303 break; 304 } 305 case QUOTA_IDTYPE_USER: 306 { 307 struct passwd *pw; 308 setpwent(); 309 while ((pw = getpwent()) != 0) 310 (void)addid(pw->pw_uid, idtype, pw->pw_name); 311 endpwent(); 312 break; 313 } 314 default: 315 errx(1, "Unknown quota ID type %d", idtype); 316 } 317 318 time(&now); 319 320 if (multiple++) 321 printf("\n"); 322 if (vflag) 323 printf("*** Report for %s quotas on %s (%s: %s)\n", 324 repquota_idtype_names[idtype], quota_getmountpoint(qh), 325 quota_getmountdevice(qh), quota_getimplname(qh)); 326 printf(" Block limits " 327 "File limits\n"); 328 printf(idtype == QUOTA_IDTYPE_USER ? "User " : "Group"); 329 printf(" used soft hard grace used" 330 " soft hard grace\n"); 331 for (id = 0; id <= highid[idtype]; id++) { 332 fup = qremove(id, idtype); 333 q = fup->fu_qv; 334 if (fup == 0) 335 continue; 336 for (i = 0; i < REPQUOTA_NUMOBJTYPES; i++) { 337 if (isover(&q[i], now)) { 338 timemsg[i] = timeprt(b0[i], 8, now, 339 q[i].qv_expiretime); 340 overchar[i] = '+'; 341 } else { 342 if (vflag && q[i].qv_grace != QUOTA_NOTIME) { 343 timemsg[i] = timeprt(b0[i], 8, 0, 344 q[i].qv_grace); 345 } else { 346 timemsg[i] = ""; 347 } 348 overchar[i] = '-'; 349 } 350 } 351 352 ok = 1; 353 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) { 354 if (q[objtype].qv_usage != 0 || 355 overchar[objtype] != '-') { 356 ok = 0; 357 } 358 } 359 if (ok && vflag == 0) 360 continue; 361 if (strlen(fup->fu_name) > 9) 362 printf("%s ", fup->fu_name); 363 else 364 printf("%-10s", fup->fu_name); 365 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) { 366 printf("%c", overchar[objtype]); 367 } 368 for (objtype = 0; objtype < REPQUOTA_NUMOBJTYPES; objtype++) { 369 isbytes = quota_objtype_isbytes(qh, objtype); 370 width = isbytes ? 9 : 8; 371 printf("%*s%*s%*s%7s", 372 width, 373 intprt(b1, width+1, q[objtype].qv_usage, 374 isbytes ? HN_B : 0, hflag), 375 width, 376 intprt(b2, width+1, q[objtype].qv_softlimit, 377 isbytes ? HN_B : 0, hflag), 378 width, 379 intprt(b3, width+1, q[objtype].qv_hardlimit, 380 isbytes ? HN_B : 0, hflag), 381 timemsg[objtype]); 382 383 if (objtype + 1 < REPQUOTA_NUMOBJTYPES) { 384 printf(" "); 385 } else { 386 printf("\n"); 387 } 388 } 389 free(fup); 390 } 391 } 392 393 static void 394 exportquotaval(const struct quotaval *qv) 395 { 396 if (qv->qv_hardlimit == QUOTA_NOLIMIT) { 397 printf(" -"); 398 } else { 399 printf(" %llu", (unsigned long long)qv->qv_hardlimit); 400 } 401 402 if (qv->qv_softlimit == QUOTA_NOLIMIT) { 403 printf(" -"); 404 } else { 405 printf(" %llu", (unsigned long long)qv->qv_softlimit); 406 } 407 408 printf(" %llu", (unsigned long long)qv->qv_usage); 409 410 if (qv->qv_expiretime == QUOTA_NOTIME) { 411 printf(" -"); 412 } else { 413 printf(" %lld", (long long)qv->qv_expiretime); 414 } 415 416 if (qv->qv_grace == QUOTA_NOTIME) { 417 printf(" -"); 418 } else { 419 printf(" %lld", (long long)qv->qv_grace); 420 } 421 } 422 423 static void 424 exportquotas(void) 425 { 426 int idtype; 427 id_t id; 428 struct fileusage *fup; 429 430 /* header */ 431 printf("@format netbsd-quota-dump v1\n"); 432 printf("# idtype id objtype hard soft usage expire grace\n"); 433 434 for (idtype = 0; idtype < REPQUOTA_NUMIDTYPES; idtype++) { 435 if (valid[idtype] == 0) 436 continue; 437 438 printf("%s default block ", repquota_idtype_names[idtype]); 439 exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS]); 440 printf("\n"); 441 442 printf("%s default file ", repquota_idtype_names[idtype]); 443 exportquotaval(&defaultqv[idtype][QUOTA_OBJTYPE_FILES]); 444 printf("\n"); 445 446 for (id = 0; id <= highid[idtype]; id++) { 447 fup = qremove(id, idtype); 448 if (fup == 0) 449 continue; 450 451 printf("%s %u block ", repquota_idtype_names[idtype], 452 id); 453 exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_BLOCKS]); 454 printf("\n"); 455 456 printf("%s %u file ", repquota_idtype_names[idtype], 457 id); 458 exportquotaval(&fup->fu_qv[QUOTA_OBJTYPE_FILES]); 459 printf("\n"); 460 461 free(fup); 462 } 463 } 464 printf("@end\n"); 465 } 466 467 /* 468 * Routines to manage the file usage table. 469 * 470 * Lookup an id of a specific id type. 471 */ 472 struct fileusage * 473 lookup(uint32_t id, int idtype) 474 { 475 struct fileusage *fup; 476 477 for (fup = fuhead[idtype][id & (FUHASH-1)]; fup != 0; fup = fup->fu_next) 478 if (fup->fu_id == id) 479 return fup; 480 return NULL; 481 } 482 /* 483 * Lookup and remove an id of a specific id type. 484 */ 485 static struct fileusage * 486 qremove(uint32_t id, int idtype) 487 { 488 struct fileusage *fup, **fupp; 489 490 for (fupp = &fuhead[idtype][id & (FUHASH-1)]; *fupp != 0;) { 491 fup = *fupp; 492 if (fup->fu_id == id) { 493 *fupp = fup->fu_next; 494 return fup; 495 } 496 fupp = &fup->fu_next; 497 } 498 return NULL; 499 } 500 501 /* 502 * Add a new file usage id if it does not already exist. 503 */ 504 static struct fileusage * 505 addid(uint32_t id, int idtype, const char *name) 506 { 507 struct fileusage *fup, **fhp; 508 struct group *gr = NULL; 509 struct passwd *pw = NULL; 510 size_t len; 511 512 if ((fup = lookup(id, idtype)) != NULL) { 513 return fup; 514 } 515 if (name == NULL) { 516 switch(idtype) { 517 case QUOTA_IDTYPE_GROUP: 518 gr = getgrgid(id); 519 520 if (gr != NULL) 521 name = gr->gr_name; 522 break; 523 case QUOTA_IDTYPE_USER: 524 pw = getpwuid(id); 525 if (pw) 526 name = pw->pw_name; 527 break; 528 default: 529 errx(1, "Unknown quota ID type %d\n", idtype); 530 } 531 } 532 533 if (name) 534 len = strlen(name); 535 else 536 len = 10; 537 if ((fup = calloc(1, sizeof(*fup) + len)) == NULL) 538 err(1, "out of memory for fileusage structures"); 539 fhp = &fuhead[idtype][id & (FUHASH - 1)]; 540 fup->fu_next = *fhp; 541 *fhp = fup; 542 fup->fu_id = id; 543 if (id > highid[idtype]) 544 highid[idtype] = id; 545 if (name) { 546 memmove(fup->fu_name, name, len + 1); 547 } else { 548 snprintf(fup->fu_name, len + 1, "%u", id); 549 } 550 /* 551 * XXX nothing guarantees the default limits have been loaded yet 552 */ 553 fup->fu_qv[QUOTA_OBJTYPE_BLOCKS] = defaultqv[idtype][QUOTA_OBJTYPE_BLOCKS]; 554 fup->fu_qv[QUOTA_OBJTYPE_FILES] = defaultqv[idtype][QUOTA_OBJTYPE_FILES]; 555 return fup; 556 } 557 558 /* 559 * Check to see if target appears in list of size cnt. 560 */ 561 static int 562 oneof(const char *target, char *list[], int cnt) 563 { 564 int i; 565 566 for (i = 0; i < cnt; i++) 567 if (strcmp(target, list[i]) == 0) 568 return i; 569 return -1; 570 } 571 572 static int 573 isover(struct quotaval *qv, time_t now) 574 { 575 return (qv->qv_usage >= qv->qv_hardlimit || 576 qv->qv_usage >= qv->qv_softlimit); 577 } 578