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