1 /* $NetBSD: quota_oldfiles.c,v 1.2 2012/01/09 15:45:19 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/types.h> 36 #include <sys/stat.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <fcntl.h> 42 #include <limits.h> 43 #include <fstab.h> 44 #include <errno.h> 45 #include <err.h> 46 47 #include <ufs/ufs/quota1.h> 48 49 #include <quota.h> 50 #include "quotapvt.h" 51 52 struct oldfiles_quotacursor { 53 unsigned oqc_doingusers; 54 unsigned oqc_doinggroups; 55 56 unsigned oqc_numusers; 57 unsigned oqc_numgroups; 58 59 unsigned oqc_didusers; 60 unsigned oqc_didgroups; 61 unsigned oqc_diddefault; 62 unsigned oqc_pos; 63 unsigned oqc_didblocks; 64 }; 65 66 static uint64_t 67 dqblk_getlimit(uint32_t val) 68 { 69 if (val == 0) { 70 return QUOTA_NOLIMIT; 71 } else { 72 return val - 1; 73 } 74 } 75 76 static uint32_t 77 dqblk_setlimit(uint64_t val) 78 { 79 if (val == QUOTA_NOLIMIT && val >= 0xffffffffUL) { 80 return 0; 81 } else { 82 return (uint32_t)val + 1; 83 } 84 } 85 86 static void 87 dqblk_getblocks(const struct dqblk *dq, struct quotaval *qv) 88 { 89 qv->qv_hardlimit = dqblk_getlimit(dq->dqb_bhardlimit); 90 qv->qv_softlimit = dqblk_getlimit(dq->dqb_bsoftlimit); 91 qv->qv_usage = dq->dqb_curblocks; 92 qv->qv_expiretime = dq->dqb_btime; 93 qv->qv_grace = QUOTA_NOTIME; 94 } 95 96 static void 97 dqblk_getfiles(const struct dqblk *dq, struct quotaval *qv) 98 { 99 qv->qv_hardlimit = dqblk_getlimit(dq->dqb_ihardlimit); 100 qv->qv_softlimit = dqblk_getlimit(dq->dqb_isoftlimit); 101 qv->qv_usage = dq->dqb_curinodes; 102 qv->qv_expiretime = dq->dqb_itime; 103 qv->qv_grace = QUOTA_NOTIME; 104 } 105 106 static void 107 dqblk_putblocks(const struct quotaval *qv, struct dqblk *dq) 108 { 109 dq->dqb_bhardlimit = dqblk_setlimit(qv->qv_hardlimit); 110 dq->dqb_bsoftlimit = dqblk_setlimit(qv->qv_softlimit); 111 dq->dqb_curblocks = qv->qv_usage; 112 dq->dqb_btime = qv->qv_expiretime; 113 /* ignore qv->qv_grace */ 114 } 115 116 static void 117 dqblk_putfiles(const struct quotaval *qv, struct dqblk *dq) 118 { 119 dq->dqb_ihardlimit = dqblk_setlimit(qv->qv_hardlimit); 120 dq->dqb_isoftlimit = dqblk_setlimit(qv->qv_softlimit); 121 dq->dqb_curinodes = qv->qv_usage; 122 dq->dqb_itime = qv->qv_expiretime; 123 /* ignore qv->qv_grace */ 124 } 125 126 static int 127 __quota_oldfiles_open(struct quotahandle *qh, const char *path, int *fd_ret) 128 { 129 int fd; 130 131 fd = open(path, O_RDWR); 132 if (fd < 0 && (errno == EACCES || errno == EROFS)) { 133 fd = open(path, O_RDONLY); 134 if (fd < 0) { 135 return -1; 136 } 137 } 138 *fd_ret = fd; 139 return 0; 140 } 141 142 int 143 __quota_oldfiles_initialize(struct quotahandle *qh) 144 { 145 static const char *const names[] = INITQFNAMES; 146 147 struct fstab *fs; 148 char buf[sizeof(fs->fs_mntops)]; 149 char *opt, *state, *s; 150 char path[PATH_MAX]; 151 const char *userquotafile, *groupquotafile; 152 int hasuserquota, hasgroupquota; 153 154 if (qh->qh_hasoldfiles) { 155 /* already initialized */ 156 return 0; 157 } 158 159 /* 160 * Find the fstab entry. 161 * 162 * XXX: should be able to handle not just ffs quota1 files but 163 * also lfs and even ext2fs. 164 */ 165 setfsent(); 166 while ((fs = getfsent()) != NULL) { 167 if (!strcmp(fs->fs_vfstype, "ffs") && 168 !strcmp(fs->fs_file, qh->qh_mountpoint)) { 169 break; 170 } 171 } 172 endfsent(); 173 174 if (fs == NULL) { 175 warnx("%s not found in fstab", qh->qh_mountpoint); 176 errno = ENXIO; 177 return -1; 178 } 179 180 /* 181 * Inspect the mount options to find the quota files. 182 * XXX this info should be gotten from the kernel. 183 * 184 * The options are: 185 * userquota[=path] enable user quotas 186 * groupquota[=path] enable group quotas 187 */ 188 hasuserquota = hasgroupquota = 0; 189 userquotafile = groupquotafile = NULL; 190 strlcpy(buf, fs->fs_mntops, sizeof(buf)); 191 for (opt = strtok_r(buf, ",", &state); 192 opt != NULL; 193 opt = strtok_r(NULL, ",", &state)) { 194 s = strchr(opt, '='); 195 if (s != NULL) { 196 *(s++) = '\0'; 197 } 198 if (!strcmp(opt, "userquota")) { 199 hasuserquota = 1; 200 if (s != NULL) { 201 userquotafile = s; 202 } 203 } else if (!strcmp(opt, "groupquota")) { 204 hasgroupquota = 1; 205 if (s != NULL) { 206 groupquotafile = s; 207 } 208 } 209 } 210 211 if (!hasuserquota && !hasgroupquota) { 212 errno = ENXIO; 213 return -1; 214 } 215 216 if (hasuserquota) { 217 if (userquotafile == NULL) { 218 (void)snprintf(path, sizeof(path), "%s/%s.%s", 219 fs->fs_file, 220 QUOTAFILENAME, names[USRQUOTA]); 221 userquotafile = path; 222 } 223 if (__quota_oldfiles_open(qh, userquotafile, 224 &qh->qh_userfile)) { 225 return -1; 226 } 227 } 228 if (hasgroupquota) { 229 if (groupquotafile == NULL) { 230 (void)snprintf(path, sizeof(path), "%s/%s.%s", 231 fs->fs_file, 232 QUOTAFILENAME, names[GRPQUOTA]); 233 groupquotafile = path; 234 } 235 if (__quota_oldfiles_open(qh, groupquotafile, 236 &qh->qh_groupfile)) { 237 return -1; 238 } 239 } 240 241 qh->qh_hasoldfiles = 1; 242 243 return 0; 244 } 245 246 const char * 247 __quota_oldfiles_getimplname(struct quotahandle *qh) 248 { 249 return "ffs quota1 direct file access"; 250 } 251 252 static int 253 __quota_oldfiles_doget(struct quotahandle *qh, const struct quotakey *qk, 254 struct quotaval *qv, int *isallzero) 255 { 256 int file; 257 off_t pos; 258 struct dqblk dq; 259 ssize_t result; 260 261 switch (qk->qk_idtype) { 262 case QUOTA_IDTYPE_USER: 263 file = qh->qh_userfile; 264 break; 265 case QUOTA_IDTYPE_GROUP: 266 file = qh->qh_groupfile; 267 break; 268 default: 269 errno = EINVAL; 270 return -1; 271 } 272 273 if (qk->qk_id == QUOTA_DEFAULTID) { 274 pos = 0; 275 } else { 276 pos = qk->qk_id * sizeof(struct dqblk); 277 } 278 279 result = pread(file, &dq, sizeof(dq), pos); 280 if (result < 0) { 281 return -1; 282 } else if (result == 0) { 283 /* Past EOF; no quota info on file for this ID */ 284 errno = ENOENT; 285 return -1; 286 } else if ((size_t)result != sizeof(dq)) { 287 errno = EFTYPE; 288 return -1; 289 } 290 291 switch (qk->qk_objtype) { 292 case QUOTA_OBJTYPE_BLOCKS: 293 dqblk_getblocks(&dq, qv); 294 break; 295 case QUOTA_OBJTYPE_FILES: 296 dqblk_getfiles(&dq, qv); 297 break; 298 default: 299 errno = EINVAL; 300 return -1; 301 } 302 303 if (qk->qk_id == QUOTA_DEFAULTID) { 304 qv->qv_usage = 0; 305 qv->qv_grace = qv->qv_expiretime; 306 qv->qv_expiretime = QUOTA_NOTIME; 307 } else if (qk->qk_id == 0) { 308 qv->qv_hardlimit = 0; 309 qv->qv_softlimit = 0; 310 qv->qv_expiretime = QUOTA_NOTIME; 311 qv->qv_grace = QUOTA_NOTIME; 312 } 313 314 if (isallzero != NULL) { 315 if (dq.dqb_bhardlimit == 0 && 316 dq.dqb_bsoftlimit == 0 && 317 dq.dqb_curblocks == 0 && 318 dq.dqb_ihardlimit == 0 && 319 dq.dqb_isoftlimit == 0 && 320 dq.dqb_curinodes == 0 && 321 dq.dqb_btime == 0 && 322 dq.dqb_itime == 0) { 323 *isallzero = 1; 324 } else { 325 *isallzero = 0; 326 } 327 } 328 329 return 0; 330 } 331 332 static int 333 __quota_oldfiles_doput(struct quotahandle *qh, const struct quotakey *qk, 334 const struct quotaval *qv) 335 { 336 int file; 337 off_t pos; 338 struct quotaval qv2; 339 struct dqblk dq; 340 ssize_t result; 341 342 switch (qk->qk_idtype) { 343 case QUOTA_IDTYPE_USER: 344 file = qh->qh_userfile; 345 break; 346 case QUOTA_IDTYPE_GROUP: 347 file = qh->qh_groupfile; 348 break; 349 default: 350 errno = EINVAL; 351 return -1; 352 } 353 354 if (qk->qk_id == QUOTA_DEFAULTID) { 355 pos = 0; 356 } else { 357 pos = qk->qk_id * sizeof(struct dqblk); 358 } 359 360 result = pread(file, &dq, sizeof(dq), pos); 361 if (result < 0) { 362 return -1; 363 } else if (result == 0) { 364 /* Past EOF; fill in a blank dq to start from */ 365 dq.dqb_bhardlimit = 0; 366 dq.dqb_bsoftlimit = 0; 367 dq.dqb_curblocks = 0; 368 dq.dqb_ihardlimit = 0; 369 dq.dqb_isoftlimit = 0; 370 dq.dqb_curinodes = 0; 371 dq.dqb_btime = 0; 372 dq.dqb_itime = 0; 373 } else if ((size_t)result != sizeof(dq)) { 374 errno = EFTYPE; 375 return -1; 376 } 377 378 switch (qk->qk_objtype) { 379 case QUOTA_OBJTYPE_BLOCKS: 380 dqblk_getblocks(&dq, &qv2); 381 break; 382 case QUOTA_OBJTYPE_FILES: 383 dqblk_getfiles(&dq, &qv2); 384 break; 385 default: 386 errno = EINVAL; 387 return -1; 388 } 389 390 if (qk->qk_id == QUOTA_DEFAULTID) { 391 qv2.qv_hardlimit = qv->qv_hardlimit; 392 qv2.qv_softlimit = qv->qv_softlimit; 393 /* leave qv2.qv_usage unchanged */ 394 qv2.qv_expiretime = qv->qv_grace; 395 /* skip qv2.qv_grace */ 396 397 /* ignore qv->qv_usage */ 398 /* ignore qv->qv_expiretime */ 399 } else if (qk->qk_id == 0) { 400 /* leave qv2.qv_hardlimit unchanged */ 401 /* leave qv2.qv_softlimit unchanged */ 402 qv2.qv_usage = qv->qv_usage; 403 /* leave qv2.qv_expiretime unchanged */ 404 /* skip qv2.qv_grace */ 405 406 /* ignore qv->qv_hardlimit */ 407 /* ignore qv->qv_softlimit */ 408 /* ignore qv->qv_expiretime */ 409 /* ignore qv->qv_grace */ 410 } else { 411 qv2 = *qv; 412 } 413 414 switch (qk->qk_objtype) { 415 case QUOTA_OBJTYPE_BLOCKS: 416 dqblk_putblocks(&qv2, &dq); 417 break; 418 case QUOTA_OBJTYPE_FILES: 419 dqblk_putfiles(&qv2, &dq); 420 break; 421 default: 422 errno = EINVAL; 423 return -1; 424 } 425 426 result = pwrite(file, &dq, sizeof(dq), pos); 427 if (result < 0) { 428 return -1; 429 } else if ((size_t)result != sizeof(dq)) { 430 /* ? */ 431 errno = EFTYPE; 432 return -1; 433 } 434 435 return 0; 436 } 437 438 int 439 __quota_oldfiles_get(struct quotahandle *qh, const struct quotakey *qk, 440 struct quotaval *qv) 441 { 442 return __quota_oldfiles_doget(qh, qk, qv, NULL); 443 } 444 445 int 446 __quota_oldfiles_put(struct quotahandle *qh, const struct quotakey *qk, 447 const struct quotaval *qv) 448 { 449 return __quota_oldfiles_doput(qh, qk, qv); 450 } 451 452 int 453 __quota_oldfiles_delete(struct quotahandle *qh, const struct quotakey *qk) 454 { 455 struct quotaval qv; 456 457 quotaval_clear(&qv); 458 return __quota_oldfiles_doput(qh, qk, &qv); 459 } 460 461 struct oldfiles_quotacursor * 462 __quota_oldfiles_cursor_create(struct quotahandle *qh) 463 { 464 struct oldfiles_quotacursor *oqc; 465 struct stat st; 466 int serrno; 467 468 oqc = malloc(sizeof(*oqc)); 469 if (oqc == NULL) { 470 return NULL; 471 } 472 473 oqc->oqc_didusers = 0; 474 oqc->oqc_didgroups = 0; 475 oqc->oqc_diddefault = 0; 476 oqc->oqc_pos = 0; 477 oqc->oqc_didblocks = 0; 478 479 if (qh->qh_userfile >= 0) { 480 oqc->oqc_doingusers = 1; 481 } else { 482 oqc->oqc_doingusers = 0; 483 oqc->oqc_didusers = 1; 484 } 485 486 if (qh->qh_groupfile >= 0) { 487 oqc->oqc_doinggroups = 1; 488 } else { 489 oqc->oqc_doinggroups = 0; 490 oqc->oqc_didgroups = 1; 491 } 492 493 if (fstat(qh->qh_userfile, &st) < 0) { 494 serrno = errno; 495 free(oqc); 496 errno = serrno; 497 return NULL; 498 } 499 oqc->oqc_numusers = st.st_size / sizeof(struct dqblk); 500 501 if (fstat(qh->qh_groupfile, &st) < 0) { 502 serrno = errno; 503 free(oqc); 504 errno = serrno; 505 return NULL; 506 } 507 oqc->oqc_numgroups = st.st_size / sizeof(struct dqblk); 508 509 return oqc; 510 } 511 512 void 513 __quota_oldfiles_cursor_destroy(struct oldfiles_quotacursor *oqc) 514 { 515 free(oqc); 516 } 517 518 int 519 __quota_oldfiles_cursor_skipidtype(struct oldfiles_quotacursor *oqc, 520 unsigned idtype) 521 { 522 switch (idtype) { 523 case QUOTA_IDTYPE_USER: 524 oqc->oqc_doingusers = 0; 525 oqc->oqc_didusers = 1; 526 break; 527 case QUOTA_IDTYPE_GROUP: 528 oqc->oqc_doinggroups = 0; 529 oqc->oqc_didgroups = 1; 530 break; 531 default: 532 errno = EINVAL; 533 return -1; 534 } 535 return 0; 536 } 537 538 int 539 __quota_oldfiles_cursor_get(struct quotahandle *qh, 540 struct oldfiles_quotacursor *oqc, 541 struct quotakey *key, struct quotaval *val) 542 { 543 unsigned maxpos; 544 int isallzero; 545 546 /* in case one of the sizes is zero */ 547 if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) { 548 oqc->oqc_didusers = 1; 549 } 550 if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) { 551 oqc->oqc_didgroups = 1; 552 } 553 554 again: 555 /* 556 * Figure out what to get 557 */ 558 559 if (!oqc->oqc_didusers) { 560 key->qk_idtype = QUOTA_IDTYPE_USER; 561 maxpos = oqc->oqc_numusers; 562 } else if (!oqc->oqc_didgroups) { 563 key->qk_idtype = QUOTA_IDTYPE_GROUP; 564 maxpos = oqc->oqc_numgroups; 565 } else { 566 errno = ENOENT; 567 return -1; 568 } 569 570 if (!oqc->oqc_diddefault) { 571 key->qk_id = QUOTA_DEFAULTID; 572 } else { 573 key->qk_id = oqc->oqc_pos; 574 } 575 576 if (!oqc->oqc_didblocks) { 577 key->qk_objtype = QUOTA_OBJTYPE_BLOCKS; 578 } else { 579 key->qk_objtype = QUOTA_OBJTYPE_FILES; 580 } 581 582 /* 583 * Get it 584 */ 585 586 if (__quota_oldfiles_doget(qh, key, val, &isallzero)) { 587 return -1; 588 } 589 590 /* 591 * Advance the cursor 592 */ 593 if (!oqc->oqc_didblocks) { 594 oqc->oqc_didblocks = 1; 595 } else { 596 oqc->oqc_didblocks = 0; 597 if (!oqc->oqc_diddefault) { 598 oqc->oqc_diddefault = 1; 599 } else { 600 oqc->oqc_pos++; 601 if (oqc->oqc_pos >= maxpos) { 602 oqc->oqc_pos = 0; 603 oqc->oqc_diddefault = 0; 604 if (!oqc->oqc_didusers) { 605 oqc->oqc_didusers = 1; 606 } else { 607 oqc->oqc_didgroups = 1; 608 } 609 } 610 } 611 } 612 613 /* 614 * If we got an all-zero dqblk (e.g. from the middle of a hole 615 * in the quota file) don't bother returning it to the caller. 616 * 617 * ...unless we're at the end of the data, to avoid going past 618 * the end and generating a spurious failure. There's no 619 * reasonable way to make _atend detect empty entries at the 620 * end of the quota files. 621 */ 622 if (isallzero && (!oqc->oqc_didusers || !oqc->oqc_didgroups)) { 623 goto again; 624 } 625 return 0; 626 } 627 628 int 629 __quota_oldfiles_cursor_getn(struct quotahandle *qh, 630 struct oldfiles_quotacursor *oqc, 631 struct quotakey *keys, struct quotaval *vals, 632 unsigned maxnum) 633 { 634 unsigned i; 635 636 if (maxnum > INT_MAX) { 637 /* joker, eh? */ 638 errno = EINVAL; 639 return -1; 640 } 641 642 for (i=0; i<maxnum; i++) { 643 if (__quota_oldfiles_cursor_atend(oqc)) { 644 break; 645 } 646 if (__quota_oldfiles_cursor_get(qh, oqc, &keys[i], &vals[i])) { 647 if (i > 0) { 648 /* 649 * Succeed witih what we have so far; 650 * the next attempt will hit the same 651 * error again. 652 */ 653 break; 654 } 655 return -1; 656 } 657 } 658 return i; 659 660 } 661 662 int 663 __quota_oldfiles_cursor_atend(struct oldfiles_quotacursor *oqc) 664 { 665 /* in case one of the sizes is zero */ 666 if (!oqc->oqc_didusers && oqc->oqc_pos >= oqc->oqc_numusers) { 667 oqc->oqc_didusers = 1; 668 } 669 if (!oqc->oqc_didgroups && oqc->oqc_pos >= oqc->oqc_numgroups) { 670 oqc->oqc_didgroups = 1; 671 } 672 673 return oqc->oqc_didusers && oqc->oqc_didgroups; 674 } 675 676 int 677 __quota_oldfiles_cursor_rewind(struct oldfiles_quotacursor *oqc) 678 { 679 oqc->oqc_didusers = 0; 680 oqc->oqc_didgroups = 0; 681 oqc->oqc_diddefault = 0; 682 oqc->oqc_pos = 0; 683 oqc->oqc_didblocks = 0; 684 return 0; 685 } 686