1/* $NetBSD$ */ 2/* 3 * Copyright (c) 1980, 1990, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * Robert Elz at The University of Melbourne. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34#include <sys/cdefs.h> 35#ifndef lint 36__COPYRIGHT("@(#) Copyright (c) 1980, 1990, 1993\ 37 The Regents of the University of California. All rights reserved."); 38#endif /* not lint */ 39 40#ifndef lint 41#if 0 42static char sccsid[] = "from: @(#)edquota.c 8.3 (Berkeley) 4/27/95"; 43#else 44__RCSID("$NetBSD$"); 45#endif 46#endif /* not lint */ 47 48/* 49 * Disk quota editor. 50 */ 51#include <sys/param.h> 52#include <sys/stat.h> 53#include <sys/file.h> 54#include <sys/wait.h> 55#include <sys/queue.h> 56#include <sys/types.h> 57#include <sys/statvfs.h> 58 59#include <quota.h> 60 61#include <assert.h> 62#include <err.h> 63#include <errno.h> 64#include <fstab.h> 65#include <pwd.h> 66#include <grp.h> 67#include <ctype.h> 68#include <signal.h> 69#include <stdbool.h> 70#include <stdio.h> 71#include <stdlib.h> 72#include <string.h> 73#include <unistd.h> 74 75#include "printquota.h" 76 77#include "pathnames.h" 78 79/* 80 * XXX. Ideally we shouldn't compile this in, but it'll take some 81 * reworking to avoid it and it'll be ok for now. 82 */ 83#define EDQUOTA_NUMOBJTYPES 2 84 85#if 0 86static const char *quotagroup = QUOTAGROUP; 87#endif 88 89#define MAX_TMPSTR (100+MAXPATHLEN) 90 91enum sources { 92 SRC_EDITED, /* values came from user */ 93 SRC_QUOTA, /* values came from a specific quota entry */ 94 SRC_DEFAULT, /* values were copied from the default quota entry */ 95 SRC_CLEAR, /* values arose by zeroing out a quota entry */ 96}; 97 98struct quotause { 99 struct quotause *next; 100 unsigned found:1, /* found after running editor */ 101 xgrace:1, /* grace periods are per-id */ 102 isdefault:1; 103 104 struct quotaval qv[EDQUOTA_NUMOBJTYPES]; 105 enum sources source[EDQUOTA_NUMOBJTYPES]; 106 char fsname[MAXPATHLEN + 1]; 107 char implementation[32]; 108}; 109 110struct quotalist { 111 struct quotause *head; 112 struct quotause *tail; 113 char *idtypename; 114}; 115 116static void usage(void) __dead; 117 118static int Hflag = 0; 119 120/* more compact form of constants */ 121#define QO_BLK QUOTA_OBJTYPE_BLOCKS 122#define QO_FL QUOTA_OBJTYPE_FILES 123 124//////////////////////////////////////////////////////////// 125// support code 126 127/* 128 * This routine converts a name for a particular quota class to 129 * an identifier. This routine must agree with the kernel routine 130 * getinoquota as to the interpretation of quota classes. 131 */ 132static int 133getidbyname(const char *name, int idtype) 134{ 135 struct passwd *pw; 136 struct group *gr; 137 138 if (alldigits(name)) 139 return atoi(name); 140 switch (idtype) { 141 case QUOTA_IDTYPE_USER: 142 if ((pw = getpwnam(name)) != NULL) 143 return pw->pw_uid; 144 warnx("%s: no such user", name); 145 break; 146 case QUOTA_IDTYPE_GROUP: 147 if ((gr = getgrnam(name)) != NULL) 148 return gr->gr_gid; 149 warnx("%s: no such group", name); 150 break; 151 default: 152 warnx("%d: unknown quota type", idtype); 153 break; 154 } 155 sleep(1); 156 return -1; 157} 158 159/* 160 * check if a source is "real" (reflects actual data) or not 161 */ 162static bool 163source_is_real(enum sources source) 164{ 165 switch (source) { 166 case SRC_EDITED: 167 case SRC_QUOTA: 168 return true; 169 case SRC_DEFAULT: 170 case SRC_CLEAR: 171 return false; 172 } 173 assert(!"encountered invalid source"); 174 return false; 175} 176 177/* 178 * some simple string tools 179 */ 180 181static /*const*/ char * 182skipws(/*const*/ char *s) 183{ 184 while (*s == ' ' || *s == '\t') { 185 s++; 186 } 187 return s; 188} 189 190static /*const*/ char * 191skipword(/*const*/ char *s) 192{ 193 while (*s != '\0' && *s != '\n' && *s != ' ' && *s != '\t') { 194 s++; 195 } 196 return s; 197} 198 199//////////////////////////////////////////////////////////// 200// quotause operations 201 202/* 203 * Create an empty quotause structure. 204 */ 205static struct quotause * 206quotause_create(void) 207{ 208 struct quotause *qup; 209 unsigned i; 210 211 qup = malloc(sizeof(*qup)); 212 if (qup == NULL) { 213 err(1, "malloc"); 214 } 215 216 qup->next = NULL; 217 qup->found = 0; 218 qup->xgrace = 0; 219 qup->isdefault = 0; 220 for (i=0; i<EDQUOTA_NUMOBJTYPES; i++) { 221 quotaval_clear(&qup->qv[i]); 222 qup->source[i] = SRC_CLEAR; 223 } 224 qup->fsname[0] = '\0'; 225 226 return qup; 227} 228 229/* 230 * Free a quotause structure. 231 */ 232static void 233quotause_destroy(struct quotause *qup) 234{ 235 free(qup); 236} 237 238//////////////////////////////////////////////////////////// 239// quotalist operations 240 241/* 242 * Create a quotause list. 243 */ 244static struct quotalist * 245quotalist_create(void) 246{ 247 struct quotalist *qlist; 248 249 qlist = malloc(sizeof(*qlist)); 250 if (qlist == NULL) { 251 err(1, "malloc"); 252 } 253 254 qlist->head = NULL; 255 qlist->tail = NULL; 256 qlist->idtypename = NULL; 257 258 return qlist; 259} 260 261/* 262 * Free a list of quotause structures. 263 */ 264static void 265quotalist_destroy(struct quotalist *qlist) 266{ 267 struct quotause *qup, *nextqup; 268 269 for (qup = qlist->head; qup; qup = nextqup) { 270 nextqup = qup->next; 271 quotause_destroy(qup); 272 } 273 free(qlist->idtypename); 274 free(qlist); 275} 276 277#if 0 278static bool 279quotalist_empty(struct quotalist *qlist) 280{ 281 return qlist->head == NULL; 282} 283#endif 284 285static void 286quotalist_append(struct quotalist *qlist, struct quotause *qup) 287{ 288 /* should not already be on a list */ 289 assert(qup->next == NULL); 290 291 if (qlist->head == NULL) { 292 qlist->head = qup; 293 } else { 294 qlist->tail->next = qup; 295 } 296 qlist->tail = qup; 297} 298 299//////////////////////////////////////////////////////////// 300// ffs quota v1 301 302#if 0 303static void 304putprivs1(uint32_t id, int idtype, struct quotause *qup) 305{ 306 struct dqblk dqblk; 307 int fd; 308 309 quotavals_to_dqblk(&qup->qv[QUOTA_LIMIT_BLOCK], 310 &qup->qv[QUOTA_LIMIT_FILE], 311 &dqblk); 312 assert((qup->flags & DEFAULT) == 0); 313 314 if ((fd = open(qup->qfname, O_WRONLY)) < 0) { 315 warnx("open `%s'", qup->qfname); 316 } else { 317 (void)lseek(fd, 318 (off_t)(id * (long)sizeof (struct dqblk)), 319 SEEK_SET); 320 if (write(fd, &dqblk, sizeof (struct dqblk)) != 321 sizeof (struct dqblk)) 322 warnx("writing `%s'", qup->qfname); 323 close(fd); 324 } 325} 326 327static struct quotause * 328getprivs1(long id, int idtype, const char *filesys) 329{ 330 struct fstab *fs; 331 char qfpathname[MAXPATHLEN]; 332 struct quotause *qup; 333 struct dqblk dqblk; 334 int fd; 335 336 setfsent(); 337 while ((fs = getfsent()) != NULL) { 338 if (strcmp(fs->fs_vfstype, "ffs")) 339 continue; 340 if (strcmp(fs->fs_spec, filesys) == 0 || 341 strcmp(fs->fs_file, filesys) == 0) 342 break; 343 } 344 if (fs == NULL) 345 return NULL; 346 347 if (!hasquota(qfpathname, sizeof(qfpathname), fs, 348 quota_idtype_to_ufs(idtype))) 349 return NULL; 350 351 qup = quotause_create(); 352 strcpy(qup->fsname, fs->fs_file); 353 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 354 fd = open(qfpathname, O_RDWR|O_CREAT, 0640); 355 if (fd < 0 && errno != ENOENT) { 356 warnx("open `%s'", qfpathname); 357 quotause_destroy(qup); 358 return NULL; 359 } 360 warnx("Creating quota file %s", qfpathname); 361 sleep(3); 362 (void)fchown(fd, getuid(), 363 getidbyname(quotagroup, QUOTA_CLASS_GROUP)); 364 (void)fchmod(fd, 0640); 365 } 366 (void)lseek(fd, (off_t)(id * sizeof(struct dqblk)), 367 SEEK_SET); 368 switch (read(fd, &dqblk, sizeof(struct dqblk))) { 369 case 0: /* EOF */ 370 /* 371 * Convert implicit 0 quota (EOF) 372 * into an explicit one (zero'ed dqblk) 373 */ 374 memset(&dqblk, 0, sizeof(struct dqblk)); 375 break; 376 377 case sizeof(struct dqblk): /* OK */ 378 break; 379 380 default: /* ERROR */ 381 warn("read error in `%s'", qfpathname); 382 close(fd); 383 quotause_destroy(qup); 384 return NULL; 385 } 386 close(fd); 387 qup->qfname = qfpathname; 388 endfsent(); 389 dqblk_to_quotavals(&dqblk, 390 &qup->qv[QUOTA_LIMIT_BLOCK], 391 &qup->qv[QUOTA_LIMIT_FILE]); 392 return qup; 393} 394#endif 395 396//////////////////////////////////////////////////////////// 397// generic quota interface 398 399static int 400dogetprivs2(struct quotahandle *qh, int idtype, id_t id, int defaultq, 401 int objtype, struct quotause *qup) 402{ 403 struct quotakey qk; 404 405 qk.qk_idtype = idtype; 406 qk.qk_id = defaultq ? QUOTA_DEFAULTID : id; 407 qk.qk_objtype = objtype; 408 if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) { 409 /* succeeded */ 410 qup->source[objtype] = SRC_QUOTA; 411 return 0; 412 } 413 if (errno != ENOENT) { 414 /* serious failure */ 415 return -1; 416 } 417 418 /* no entry, get default entry */ 419 qk.qk_id = QUOTA_DEFAULTID; 420 if (quota_get(qh, &qk, &qup->qv[objtype]) == 0) { 421 /* succeeded */ 422 qup->source[objtype] = SRC_DEFAULT; 423 return 0; 424 } 425 if (errno != ENOENT) { 426 return -1; 427 } 428 429 /* use a zeroed-out entry */ 430 quotaval_clear(&qup->qv[objtype]); 431 qup->source[objtype] = SRC_CLEAR; 432 return 0; 433} 434 435static struct quotause * 436getprivs2(long id, int idtype, const char *filesys, int defaultq, 437 char **idtypename_p) 438{ 439 struct quotause *qup; 440 struct quotahandle *qh; 441 const char *impl; 442 unsigned restrictions; 443 const char *idtypename; 444 int serrno; 445 446 qup = quotause_create(); 447 strcpy(qup->fsname, filesys); 448 if (defaultq) 449 qup->isdefault = 1; 450 451 qh = quota_open(filesys); 452 if (qh == NULL) { 453 serrno = errno; 454 quotause_destroy(qup); 455 errno = serrno; 456 return NULL; 457 } 458 459 impl = quota_getimplname(qh); 460 if (impl == NULL) { 461 impl = "???"; 462 } 463 strlcpy(qup->implementation, impl, sizeof(qup->implementation)); 464 465 restrictions = quota_getrestrictions(qh); 466 if ((restrictions & QUOTA_RESTRICT_UNIFORMGRACE) == 0) { 467 qup->xgrace = 1; 468 } 469 470 if (*idtypename_p == NULL) { 471 idtypename = quota_idtype_getname(qh, idtype); 472 *idtypename_p = strdup(idtypename); 473 if (*idtypename_p == NULL) { 474 errx(1, "Out of memory"); 475 } 476 } 477 478 if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_BLOCKS, qup)) { 479 serrno = errno; 480 quota_close(qh); 481 quotause_destroy(qup); 482 errno = serrno; 483 return NULL; 484 } 485 486 if (dogetprivs2(qh, idtype, id, defaultq, QUOTA_OBJTYPE_FILES, qup)) { 487 serrno = errno; 488 quota_close(qh); 489 quotause_destroy(qup); 490 errno = serrno; 491 return NULL; 492 } 493 494 quota_close(qh); 495 496 return qup; 497} 498 499static void 500putprivs2(uint32_t id, int idtype, struct quotause *qup) 501{ 502 struct quotahandle *qh; 503 struct quotakey qk; 504 char idname[32]; 505 506 if (qup->isdefault) { 507 snprintf(idname, sizeof(idname), "%s default", 508 idtype == QUOTA_IDTYPE_USER ? "user" : "group"); 509 id = QUOTA_DEFAULTID; 510 } else { 511 snprintf(idname, sizeof(idname), "%s %u", 512 idtype == QUOTA_IDTYPE_USER ? "uid" : "gid", id); 513 } 514 515 qh = quota_open(qup->fsname); 516 if (qh == NULL) { 517 err(1, "%s: quota_open", qup->fsname); 518 } 519 520 if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) { 521 qk.qk_idtype = idtype; 522 qk.qk_id = id; 523 qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS; 524 if (quota_put(qh, &qk, &qup->qv[QO_BLK])) { 525 err(1, "%s: quota_put (%s blocks)", qup->fsname, 526 idname); 527 } 528 } 529 530 if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) { 531 qk.qk_idtype = idtype; 532 qk.qk_id = id; 533 qk.qk_objtype = QUOTA_OBJTYPE_FILES; 534 if (quota_put(qh, &qk, &qup->qv[QO_FL])) { 535 err(1, "%s: quota_put (%s files)", qup->fsname, 536 idname); 537 } 538 } 539 540 quota_close(qh); 541} 542 543//////////////////////////////////////////////////////////// 544// quota format switch 545 546/* 547 * Collect the requested quota information. 548 */ 549static struct quotalist * 550getprivs(long id, int defaultq, int idtype, const char *filesys) 551{ 552 struct statvfs *fst; 553 int nfst, i; 554 struct quotalist *qlist; 555 struct quotause *qup; 556 int seenany = 0; 557 558 qlist = quotalist_create(); 559 560 nfst = getmntinfo(&fst, MNT_WAIT); 561 if (nfst == 0) 562 errx(1, "no filesystems mounted!"); 563 564 for (i = 0; i < nfst; i++) { 565 if ((fst[i].f_flag & ST_QUOTA) == 0) 566 continue; 567 seenany = 1; 568 if (filesys && 569 strcmp(fst[i].f_mntonname, filesys) != 0 && 570 strcmp(fst[i].f_mntfromname, filesys) != 0) 571 continue; 572 qup = getprivs2(id, idtype, fst[i].f_mntonname, defaultq, 573 &qlist->idtypename); 574 if (qup == NULL) { 575 /* XXX the atf tests demand failing silently */ 576 /*warn("Reading quotas failed for id %ld", id);*/ 577 continue; 578 } 579 580 quotalist_append(qlist, qup); 581 } 582 583 if (!seenany) { 584 errx(1, "No mounted filesystems have quota support"); 585 } 586 587#if 0 588 if (filesys && quotalist_empty(qlist)) { 589 if (defaultq) 590 errx(1, "no default quota for version 1"); 591 /* if we get there, filesys is not mounted. try the old way */ 592 qup = getprivs1(id, idtype, filesys); 593 if (qup == NULL) { 594 warnx("getprivs1 failed"); 595 return qlist; 596 } 597 quotalist_append(qlist, qup); 598 } 599#endif 600 return qlist; 601} 602 603/* 604 * Store the requested quota information. 605 */ 606static void 607putprivs(uint32_t id, int idtype, struct quotalist *qlist) 608{ 609 struct quotause *qup; 610 611 for (qup = qlist->head; qup; qup = qup->next) { 612#if 0 613 if (qup->qfname != NULL) 614 putprivs1(id, idtype, qup); 615 else 616#endif 617 putprivs2(id, idtype, qup); 618 } 619} 620 621static void 622clearpriv(int argc, char **argv, const char *filesys, int idtype) 623{ 624 struct statvfs *fst; 625 int nfst, i; 626 int id; 627 id_t *ids; 628 unsigned nids, maxids, j; 629 struct quotahandle *qh; 630 struct quotakey qk; 631 char idname[32]; 632 633 maxids = 4; 634 nids = 0; 635 ids = malloc(maxids * sizeof(ids[0])); 636 if (ids == NULL) { 637 err(1, "malloc"); 638 } 639 640 for ( ; argc > 0; argc--, argv++) { 641 if ((id = getidbyname(*argv, idtype)) == -1) 642 continue; 643 644 if (nids + 1 > maxids) { 645 maxids *= 2; 646 ids = realloc(ids, maxids * sizeof(ids[0])); 647 if (ids == NULL) { 648 err(1, "realloc"); 649 } 650 } 651 ids[nids++] = id; 652 } 653 654 /* now loop over quota-enabled filesystems */ 655 nfst = getmntinfo(&fst, MNT_WAIT); 656 if (nfst == 0) 657 errx(1, "no filesystems mounted!"); 658 659 for (i = 0; i < nfst; i++) { 660 if ((fst[i].f_flag & ST_QUOTA) == 0) 661 continue; 662 if (filesys && strcmp(fst[i].f_mntonname, filesys) != 0 && 663 strcmp(fst[i].f_mntfromname, filesys) != 0) 664 continue; 665 666 qh = quota_open(fst[i].f_mntonname); 667 if (qh == NULL) { 668 err(1, "%s: quota_open", fst[i].f_mntonname); 669 } 670 671 for (j = 0; j < nids; j++) { 672 snprintf(idname, sizeof(idname), "%s %u", 673 idtype == QUOTA_IDTYPE_USER ? 674 "uid" : "gid", ids[j]); 675 676 qk.qk_idtype = idtype; 677 qk.qk_id = ids[j]; 678 qk.qk_objtype = QUOTA_OBJTYPE_BLOCKS; 679 if (quota_delete(qh, &qk)) { 680 err(1, "%s: quota_delete (%s blocks)", 681 fst[i].f_mntonname, idname); 682 } 683 684 qk.qk_idtype = idtype; 685 qk.qk_id = ids[j]; 686 qk.qk_objtype = QUOTA_OBJTYPE_FILES; 687 if (quota_delete(qh, &qk)) { 688 if (errno == ENOENT) { 689 /* 690 * XXX ignore this case; due 691 * to a weakness in the quota 692 * proplib interface it can 693 * appear spuriously. 694 */ 695 } else { 696 err(1, "%s: quota_delete (%s files)", 697 fst[i].f_mntonname, idname); 698 } 699 } 700 } 701 702 quota_close(qh); 703 } 704 705 free(ids); 706} 707 708//////////////////////////////////////////////////////////// 709// editor 710 711/* 712 * Take a list of privileges and get it edited. 713 */ 714static int 715editit(const char *ltmpfile) 716{ 717 pid_t pid; 718 int lst; 719 char p[MAX_TMPSTR]; 720 const char *ed; 721 sigset_t s, os; 722 723 sigemptyset(&s); 724 sigaddset(&s, SIGINT); 725 sigaddset(&s, SIGQUIT); 726 sigaddset(&s, SIGHUP); 727 if (sigprocmask(SIG_BLOCK, &s, &os) == -1) 728 err(1, "sigprocmask"); 729top: 730 switch ((pid = fork())) { 731 case -1: 732 if (errno == EPROCLIM) { 733 warnx("You have too many processes"); 734 return 0; 735 } 736 if (errno == EAGAIN) { 737 sleep(1); 738 goto top; 739 } 740 warn("fork"); 741 return 0; 742 case 0: 743 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1) 744 err(1, "sigprocmask"); 745 setgid(getgid()); 746 setuid(getuid()); 747 if ((ed = getenv("EDITOR")) == (char *)0) 748 ed = _PATH_VI; 749 if (strlen(ed) + strlen(ltmpfile) + 2 >= MAX_TMPSTR) { 750 errx(1, "%s", "editor or filename too long"); 751 } 752 snprintf(p, sizeof(p), "%s %s", ed, ltmpfile); 753 execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", p, NULL); 754 err(1, "%s", ed); 755 default: 756 if (waitpid(pid, &lst, 0) == -1) 757 err(1, "waitpid"); 758 if (sigprocmask(SIG_SETMASK, &os, NULL) == -1) 759 err(1, "sigprocmask"); 760 if (!WIFEXITED(lst) || WEXITSTATUS(lst) != 0) 761 return 0; 762 return 1; 763 } 764} 765 766/* 767 * Convert a quotause list to an ASCII file. 768 */ 769static int 770writeprivs(struct quotalist *qlist, int outfd, const char *name, 771 int idtype, const char *idtypename) 772{ 773 struct quotause *qup; 774 FILE *fd; 775 char b0[32], b1[32], b2[32], b3[32]; 776 const char *comm; 777 778 (void)ftruncate(outfd, 0); 779 (void)lseek(outfd, (off_t)0, SEEK_SET); 780 if ((fd = fdopen(dup(outfd), "w")) == NULL) 781 errx(1, "fdopen"); 782 if (name == NULL) { 783 fprintf(fd, "Default %s quotas:\n", idtypename); 784 } else { 785 fprintf(fd, "Quotas for %s %s:\n", idtypename, name); 786 } 787 for (qup = qlist->head; qup; qup = qup->next) { 788 struct quotaval *q = qup->qv; 789 790 fprintf(fd, "%s (%s):\n", qup->fsname, qup->implementation); 791 792 comm = source_is_real(qup->source[QO_BLK]) ? "" : "#"; 793 fprintf(fd, "\tblocks:%s\n", 794 Hflag ? "" : " (sizes in 1K-blocks)"); 795 fprintf(fd, "\t\t%susage: %s\n", comm, 796 intprt(b1, 21, q[QO_BLK].qv_usage, 797 HN_NOSPACE | HN_B, Hflag)); 798 fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm, 799 intprt(b2, 21, q[QO_BLK].qv_softlimit, 800 HN_NOSPACE | HN_B, Hflag), 801 intprt(b3, 21, q[QO_BLK].qv_hardlimit, 802 HN_NOSPACE | HN_B, Hflag)); 803 if (qup->xgrace || qup->isdefault) { 804 fprintf(fd, "\t\t%sgrace: %s\n", comm, 805 timepprt(b0, 21, q[QO_BLK].qv_grace, Hflag)); 806 } 807 808 comm = source_is_real(qup->source[QO_FL]) ? "" : "#"; 809 fprintf(fd, "\tinodes:\n"); 810 fprintf(fd, "\t\t%susage: %s\n", comm, 811 intprt(b1, 21, q[QO_FL].qv_usage, 812 HN_NOSPACE, Hflag)); 813 fprintf(fd, "\t\t%slimits: soft %s, hard %s\n", comm, 814 intprt(b2, 21, q[QO_FL].qv_softlimit, 815 HN_NOSPACE, Hflag), 816 intprt(b3, 21, q[QO_FL].qv_hardlimit, 817 HN_NOSPACE, Hflag)); 818 if (qup->xgrace || qup->isdefault) { 819 fprintf(fd, "\t\t%sgrace: %s\n", comm, 820 timepprt(b0, 21, q[QO_FL].qv_grace, Hflag)); 821 } 822 } 823 fclose(fd); 824 return 1; 825} 826 827/* 828 * Merge changes to an ASCII file into a quotause list. 829 */ 830static int 831readprivs(struct quotalist *qlist, int infd, int dflag) 832{ 833 FILE *fd; /* file */ 834 unsigned lineno; /* line number in file */ 835 char line[BUFSIZ]; /* input buffer */ 836 size_t len; /* length of input buffer */ 837 bool seenheader; /* true if past the file header */ 838 struct quotause *qup; /* current filesystem */ 839 bool haveobjtype; /* true if objtype is valid */ 840 unsigned objtype; /* current object type */ 841 int objtypeflags; /* humanize flags */ 842 /* XXX make following const later (requires non-local cleanup) */ 843 /*const*/ char *text, *text2, *t; /* scratch values */ 844 char b0[32], b1[32]; 845 uint64_t soft, hard, current; 846 time_t grace; 847 struct quotaval *qv; 848 849 lineno = 0; 850 seenheader = false; 851 qup = NULL; 852 haveobjtype = false; 853 objtype = QUOTA_OBJTYPE_BLOCKS; /* for gcc 4.5 */ 854 objtypeflags = HN_B; 855 856 (void)lseek(infd, (off_t)0, SEEK_SET); 857 fd = fdopen(dup(infd), "r"); 858 if (fd == NULL) { 859 warn("Can't re-read temp file"); 860 return -1; 861 } 862 863 /* 864 * Read back the same format we wrote. 865 */ 866 867 while (fgets(line, sizeof(line), fd)) { 868 lineno++; 869 if (!seenheader) { 870 if ((!strncmp(line, "Default ", 8) && dflag) || 871 (!strncmp(line, "Quotas for ", 11) && !dflag)) { 872 /* ok. */ 873 seenheader = 1; 874 continue; 875 } 876 warnx("Header line missing"); 877 goto fail; 878 } 879 len = strlen(line); 880 if (len == 0) { 881 /* ? */ 882 continue; 883 } 884 if (line[len - 1] != '\n') { 885 warnx("Line %u too long", lineno); 886 goto fail; 887 } 888 line[--len] = '\0'; 889 if (line[len - 1] == '\r') { 890 line[--len] = '\0'; 891 } 892 if (len == 0) { 893 /* blank line */ 894 continue; 895 } 896 897 /* 898 * If the line has: it is: 899 * two tabs values 900 * one tab the next objtype 901 * no tabs the next filesystem 902 */ 903 if (line[0] == '\t' && line[1] == '\t') { 904 if (qup == NULL) { 905 warnx("Line %u: values with no filesystem", 906 lineno); 907 goto fail; 908 } 909 if (!haveobjtype) { 910 warnx("Line %u: values with no object type", 911 lineno); 912 goto fail; 913 } 914 qv = &qup->qv[objtype]; 915 916 text = line + 2; 917 if (*text == '#') { 918 /* commented out; ignore */ 919 continue; 920 } 921 else if (!strncmp(text, "usage:", 6)) { 922 923 /* usage: %llu */ 924 text += 6; 925 t = skipws(text); 926 if (intrd(t, ¤t, objtypeflags) != 0) { 927 warnx("Line %u: Bad number %s", 928 lineno, t); 929 goto fail; 930 } 931 932 /* 933 * Because the humanization can lead 934 * to roundoff, check if the two 935 * values produce the same humanized 936 * string, rather than if they're the 937 * same number. Sigh. 938 */ 939 intprt(b0, 21, current, 940 HN_NOSPACE | objtypeflags, Hflag); 941 intprt(b1, 21, qv->qv_usage, 942 HN_NOSPACE | objtypeflags, Hflag); 943 if (strcmp(b0, b1)) { 944 warnx("Line %u: cannot change usage", 945 lineno); 946 } 947 continue; 948 949 } else if (!strncmp(text, "limits:", 7)) { 950 951 /* limits: soft %llu, hard %llu */ 952 text += 7; 953 text2 = strchr(text, ','); 954 if (text2 == NULL) { 955 warnx("Line %u: expected comma", 956 lineno); 957 goto fail; 958 } 959 *text2 = '\0'; 960 text2++; 961 962 t = skipws(text); 963 t = skipword(t); 964 t = skipws(t); 965 if (intrd(t, &soft, objtypeflags) != 0) { 966 warnx("Line %u: Bad number %s", 967 lineno, t); 968 goto fail; 969 } 970 t = skipws(text2); 971 t = skipword(t); 972 t = skipws(t); 973 if (intrd(t, &hard, objtypeflags) != 0) { 974 warnx("Line %u: Bad number %s", 975 lineno, t); 976 goto fail; 977 } 978 979 /* 980 * Cause time limit to be reset when the quota 981 * is next used if previously had no soft limit 982 * or were under it, but now have a soft limit 983 * and are over it. 984 */ 985 if (qv->qv_usage && qv->qv_usage >= soft && 986 (qv->qv_softlimit == 0 || 987 qv->qv_usage < qv->qv_softlimit)) { 988 qv->qv_expiretime = 0; 989 } 990 if (soft != qv->qv_softlimit || 991 hard != qv->qv_hardlimit) { 992 qv->qv_softlimit = soft; 993 qv->qv_hardlimit = hard; 994 qup->source[objtype] = SRC_EDITED; 995 } 996 qup->found = 1; 997 998 } else if (!strncmp(text, "grace:", 6)) { 999 1000 text += 6; 1001 /* grace: %llu */ 1002 t = skipws(text); 1003 if (timeprd(t, &grace) != 0) { 1004 warnx("Line %u: Bad number %s", 1005 lineno, t); 1006 goto fail; 1007 } 1008 if (qup->isdefault || qup->xgrace) { 1009 if (grace != qv->qv_grace) { 1010 qv->qv_grace = grace; 1011 qup->source[objtype] = 1012 SRC_EDITED; 1013 } 1014 qup->found = 1; 1015 } else { 1016 warnx("Line %u: Cannot set individual " 1017 "grace time on this filesystem", 1018 lineno); 1019 goto fail; 1020 } 1021 1022 } else { 1023 warnx("Line %u: Unknown/unexpected value line", 1024 lineno); 1025 goto fail; 1026 } 1027 } else if (line[0] == '\t') { 1028 text = line + 1; 1029 if (*text == '#') { 1030 /* commented out; ignore */ 1031 continue; 1032 } 1033 else if (!strncmp(text, "blocks:", 7)) { 1034 objtype = QUOTA_OBJTYPE_BLOCKS; 1035 objtypeflags = HN_B; 1036 haveobjtype = true; 1037 } else if (!strncmp(text, "inodes:", 7)) { 1038 objtype = QUOTA_OBJTYPE_FILES; 1039 objtypeflags = 0; 1040 haveobjtype = true; 1041 } else { 1042 warnx("Line %u: Unknown/unexpected object " 1043 "type (%s)", lineno, text); 1044 goto fail; 1045 } 1046 } else { 1047 t = strchr(line, ' '); 1048 if (t == NULL) { 1049 t = strchr(line, ':'); 1050 if (t == NULL) { 1051 t = line + len; 1052 } 1053 } 1054 *t = '\0'; 1055 1056 if (*line == '#') { 1057 /* commented out; ignore */ 1058 continue; 1059 } 1060 1061 for (qup = qlist->head; qup; qup = qup->next) { 1062 if (!strcmp(line, qup->fsname)) 1063 break; 1064 } 1065 if (qup == NULL) { 1066 warnx("Line %u: Filesystem %s invalid or has " 1067 "no quota support", lineno, line); 1068 goto fail; 1069 } 1070 haveobjtype = false; 1071 } 1072 } 1073 1074 fclose(fd); 1075 1076 /* 1077 * Disable quotas for any filesystems that we didn't see, 1078 * because they must have been deleted in the editor. 1079 * 1080 * XXX this should be improved so it results in 1081 * quota_delete(), not just writing out a blank quotaval. 1082 */ 1083 for (qup = qlist->head; qup; qup = qup->next) { 1084 if (qup->found) { 1085 qup->found = 0; 1086 continue; 1087 } 1088 1089 if (source_is_real(qup->source[QUOTA_OBJTYPE_BLOCKS])) { 1090 quotaval_clear(&qup->qv[QUOTA_OBJTYPE_BLOCKS]); 1091 qup->source[QUOTA_OBJTYPE_BLOCKS] = SRC_EDITED; 1092 } 1093 1094 if (source_is_real(qup->source[QUOTA_OBJTYPE_FILES])) { 1095 quotaval_clear(&qup->qv[QUOTA_OBJTYPE_FILES]); 1096 qup->source[QUOTA_OBJTYPE_FILES] = SRC_EDITED; 1097 } 1098 } 1099 return 0; 1100 1101fail: 1102 sleep(3); 1103 fclose(fd); 1104 return -1; 1105} 1106 1107//////////////////////////////////////////////////////////// 1108// actions 1109 1110static void 1111replicate(const char *fs, int idtype, const char *protoname, 1112 char **names, int numnames) 1113{ 1114 long protoid, id; 1115 struct quotalist *protoprivs; 1116 struct quotause *qup; 1117 int i; 1118 1119 if ((protoid = getidbyname(protoname, idtype)) == -1) 1120 exit(1); 1121 protoprivs = getprivs(protoid, 0, idtype, fs); 1122 for (qup = protoprivs->head; qup; qup = qup->next) { 1123 qup->qv[QO_BLK].qv_expiretime = 0; 1124 qup->qv[QO_FL].qv_expiretime = 0; 1125 qup->source[QO_BLK] = SRC_EDITED; 1126 qup->source[QO_FL] = SRC_EDITED; 1127 } 1128 for (i=0; i<numnames; i++) { 1129 id = getidbyname(names[i], idtype); 1130 if (id == -1) 1131 continue; 1132 putprivs(id, idtype, protoprivs); 1133 } 1134 /* XXX */ 1135 /* quotalist_destroy(protoprivs); */ 1136} 1137 1138static void 1139assign(const char *fs, int idtype, 1140 char *soft, char *hard, char *grace, 1141 char **names, int numnames) 1142{ 1143 struct quotalist *curprivs; 1144 struct quotause *lqup; 1145 u_int64_t softb, hardb, softi, hardi; 1146 time_t graceb, gracei; 1147 char *str; 1148 long id; 1149 int dflag; 1150 int i; 1151 1152 if (soft) { 1153 str = strsep(&soft, "/"); 1154 if (str[0] == '\0' || soft == NULL || soft[0] == '\0') 1155 usage(); 1156 1157 if (intrd(str, &softb, HN_B) != 0) 1158 errx(1, "%s: bad number", str); 1159 if (intrd(soft, &softi, 0) != 0) 1160 errx(1, "%s: bad number", soft); 1161 } 1162 if (hard) { 1163 str = strsep(&hard, "/"); 1164 if (str[0] == '\0' || hard == NULL || hard[0] == '\0') 1165 usage(); 1166 1167 if (intrd(str, &hardb, HN_B) != 0) 1168 errx(1, "%s: bad number", str); 1169 if (intrd(hard, &hardi, 0) != 0) 1170 errx(1, "%s: bad number", hard); 1171 } 1172 if (grace) { 1173 str = strsep(&grace, "/"); 1174 if (str[0] == '\0' || grace == NULL || grace[0] == '\0') 1175 usage(); 1176 1177 if (timeprd(str, &graceb) != 0) 1178 errx(1, "%s: bad number", str); 1179 if (timeprd(grace, &gracei) != 0) 1180 errx(1, "%s: bad number", grace); 1181 } 1182 for (i=0; i<numnames; i++) { 1183 if (names[i] == NULL) { 1184 id = 0; 1185 dflag = 1; 1186 } else { 1187 id = getidbyname(names[i], idtype); 1188 if (id == -1) 1189 continue; 1190 dflag = 0; 1191 } 1192 1193 curprivs = getprivs(id, dflag, idtype, fs); 1194 for (lqup = curprivs->head; lqup; lqup = lqup->next) { 1195 struct quotaval *q = lqup->qv; 1196 if (soft) { 1197 if (!dflag && softb && 1198 q[QO_BLK].qv_usage >= softb && 1199 (q[QO_BLK].qv_softlimit == 0 || 1200 q[QO_BLK].qv_usage < 1201 q[QO_BLK].qv_softlimit)) 1202 q[QO_BLK].qv_expiretime = 0; 1203 if (!dflag && softi && 1204 q[QO_FL].qv_usage >= softb && 1205 (q[QO_FL].qv_softlimit == 0 || 1206 q[QO_FL].qv_usage < 1207 q[QO_FL].qv_softlimit)) 1208 q[QO_FL].qv_expiretime = 0; 1209 q[QO_BLK].qv_softlimit = softb; 1210 q[QO_FL].qv_softlimit = softi; 1211 } 1212 if (hard) { 1213 q[QO_BLK].qv_hardlimit = hardb; 1214 q[QO_FL].qv_hardlimit = hardi; 1215 } 1216 if (grace) { 1217 q[QO_BLK].qv_grace = graceb; 1218 q[QO_FL].qv_grace = gracei; 1219 } 1220 lqup->source[QO_BLK] = SRC_EDITED; 1221 lqup->source[QO_FL] = SRC_EDITED; 1222 } 1223 putprivs(id, idtype, curprivs); 1224 quotalist_destroy(curprivs); 1225 } 1226} 1227 1228static void 1229clear(const char *fs, int idtype, char **names, int numnames) 1230{ 1231 clearpriv(numnames, names, fs, idtype); 1232} 1233 1234static void 1235editone(const char *fs, int idtype, const char *name, 1236 int tmpfd, const char *tmppath) 1237{ 1238 struct quotalist *curprivs; 1239 long id; 1240 int dflag; 1241 1242 if (name == NULL) { 1243 id = 0; 1244 dflag = 1; 1245 } else { 1246 id = getidbyname(name, idtype); 1247 if (id == -1) 1248 return; 1249 dflag = 0; 1250 } 1251 curprivs = getprivs(id, dflag, idtype, fs); 1252 1253 if (writeprivs(curprivs, tmpfd, name, idtype, 1254 curprivs->idtypename) == 0) 1255 goto fail; 1256 1257 if (editit(tmppath) == 0) 1258 goto fail; 1259 1260 if (readprivs(curprivs, tmpfd, dflag) < 0) 1261 goto fail; 1262 1263 putprivs(id, idtype, curprivs); 1264fail: 1265 quotalist_destroy(curprivs); 1266} 1267 1268static void 1269edit(const char *fs, int idtype, char **names, int numnames) 1270{ 1271 char tmppath[] = _PATH_TMPFILE; 1272 int tmpfd, i; 1273 1274 tmpfd = mkstemp(tmppath); 1275 fchown(tmpfd, getuid(), getgid()); 1276 1277 for (i=0; i<numnames; i++) { 1278 editone(fs, idtype, names[i], tmpfd, tmppath); 1279 } 1280 1281 close(tmpfd); 1282 unlink(tmppath); 1283} 1284 1285//////////////////////////////////////////////////////////// 1286// main 1287 1288static void 1289usage(void) 1290{ 1291 const char *p = getprogname(); 1292 fprintf(stderr, 1293 "Usage: %s [-D] [-H] [-u] [-p <username>] [-f <filesystem>] " 1294 "-d | <username> ...\n" 1295 "\t%s [-D] [-H] -g [-p <groupname>] [-f <filesystem>] " 1296 "-d | <groupname> ...\n" 1297 "\t%s [-D] [-u] [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] " 1298 "-d | <username> ...\n" 1299 "\t%s [-D] -g [-f <filesystem>] [-s b#/i#] [-h b#/i#] [-t t#/t#] " 1300 "-d | <groupname> ...\n" 1301 "\t%s [-D] [-H] [-u] -c [-f <filesystem>] username ...\n" 1302 "\t%s [-D] [-H] -g -c [-f <filesystem>] groupname ...\n", 1303 p, p, p, p, p, p); 1304 exit(1); 1305} 1306 1307int 1308main(int argc, char *argv[]) 1309{ 1310 int idtype; 1311 char *protoname; 1312 char *soft = NULL, *hard = NULL, *grace = NULL; 1313 char *fs = NULL; 1314 int ch; 1315 int pflag = 0; 1316 int cflag = 0; 1317 int dflag = 0; 1318 1319 if (argc < 2) 1320 usage(); 1321 if (getuid()) 1322 errx(1, "permission denied"); 1323 protoname = NULL; 1324 idtype = QUOTA_IDTYPE_USER; 1325 while ((ch = getopt(argc, argv, "Hcdugp:s:h:t:f:")) != -1) { 1326 switch(ch) { 1327 case 'H': 1328 Hflag++; 1329 break; 1330 case 'c': 1331 cflag++; 1332 break; 1333 case 'd': 1334 dflag++; 1335 break; 1336 case 'p': 1337 protoname = optarg; 1338 pflag++; 1339 break; 1340 case 'g': 1341 idtype = QUOTA_IDTYPE_GROUP; 1342 break; 1343 case 'u': 1344 idtype = QUOTA_IDTYPE_USER; 1345 break; 1346 case 's': 1347 soft = optarg; 1348 break; 1349 case 'h': 1350 hard = optarg; 1351 break; 1352 case 't': 1353 grace = optarg; 1354 break; 1355 case 'f': 1356 fs = optarg; 1357 break; 1358 default: 1359 usage(); 1360 } 1361 } 1362 argc -= optind; 1363 argv += optind; 1364 1365 if (pflag) { 1366 if (soft || hard || grace || dflag || cflag) 1367 usage(); 1368 replicate(fs, idtype, protoname, argv, argc); 1369 } else if (soft || hard || grace) { 1370 if (cflag) 1371 usage(); 1372 if (dflag) { 1373 /* use argv[argc], which is null, to mean 'default' */ 1374 argc++; 1375 } 1376 assign(fs, idtype, soft, hard, grace, argv, argc); 1377 } else if (cflag) { 1378 if (dflag) 1379 usage(); 1380 clear(fs, idtype, argv, argc); 1381 } else { 1382 if (dflag) { 1383 /* use argv[argc], which is null, to mean 'default' */ 1384 argc++; 1385 } 1386 edit(fs, idtype, argv, argc); 1387 } 1388 return 0; 1389} 1390