1 /* 2 * Copyright (c) 1980, 1990, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * This code is derived from software contributed to Berkeley by 6 * Robert Elz at The University of Melbourne. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 /* 34 * Disk quota editor. 35 */ 36 #include <sys/param.h> /* btodb dbtob */ 37 #include <sys/stat.h> 38 #include <sys/file.h> 39 #include <sys/wait.h> 40 #include <ufs/ufs/quota.h> 41 42 #include <ctype.h> 43 #include <err.h> 44 #include <errno.h> 45 #include <fstab.h> 46 #include <grp.h> 47 #include <paths.h> 48 #include <pwd.h> 49 #include <stdio.h> 50 #include <signal.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <unistd.h> 54 #include <limits.h> 55 56 char *qfname = QUOTAFILENAME; 57 char *qfextension[] = INITQFNAMES; 58 char *quotagroup = QUOTAGROUP; 59 char tmpfil[] = "/tmp/edquota.XXXXXXXXXX"; 60 61 struct quotause { 62 struct quotause *next; 63 long flags; 64 struct dqblk dqblk; 65 char fsname[PATH_MAX]; 66 char qfname[1]; /* actually longer */ 67 } *getprivs(u_int, int); 68 #define FOUND 0x01 69 70 void usage(void); 71 int getentry(char *, int, u_int *); 72 struct quotause * 73 getprivs(u_int, int); 74 void putprivs(long, int, struct quotause *); 75 int editit(const char *); 76 int writeprivs(struct quotause *, int, char *, int); 77 int readprivs(struct quotause *, int); 78 int writetimes(struct quotause *, int, int); 79 int readtimes(struct quotause *, int); 80 char * cvtstoa(time_t); 81 int cvtatos(long long, char *, time_t *); 82 void freeprivs(struct quotause *); 83 int alldigits(char *s); 84 int hasquota(struct fstab *, int, char **); 85 86 void 87 usage(void) 88 { 89 (void)fprintf(stderr, "%s%s%s%s", 90 "usage: edquota [-u] [-p proto-username] username | uid ...\n", 91 " edquota -g [-p proto-groupname] groupname | gid ...\n", 92 " edquota -t [-u]\n", 93 " edquota -g -t\n"); 94 exit(1); 95 } 96 97 int 98 main(int argc, char *argv[]) 99 { 100 struct quotause *qup, *protoprivs, *curprivs; 101 u_int id, protoid; 102 int quotatype, tmpfd; 103 char *protoname = NULL; 104 int ch; 105 int tflag = 0, pflag = 0; 106 107 if (argc < 2) 108 usage(); 109 if (getuid()) 110 errx(1, "%s", strerror(EPERM)); 111 quotatype = USRQUOTA; 112 while ((ch = getopt(argc, argv, "ugtp:")) != -1) { 113 switch(ch) { 114 case 'p': 115 protoname = optarg; 116 pflag = 1; 117 break; 118 case 'g': 119 quotatype = GRPQUOTA; 120 break; 121 case 'u': 122 quotatype = USRQUOTA; 123 break; 124 case 't': 125 tflag = 1; 126 break; 127 default: 128 usage(); 129 } 130 } 131 argc -= optind; 132 argv += optind; 133 if (pflag) { 134 if (getentry(protoname, quotatype, &protoid) == -1) 135 exit(1); 136 protoprivs = getprivs(protoid, quotatype); 137 for (qup = protoprivs; qup; qup = qup->next) { 138 qup->dqblk.dqb_btime = 0; 139 qup->dqblk.dqb_itime = 0; 140 } 141 while (argc-- > 0) { 142 if (getentry(*argv++, quotatype, &id) == -1) 143 continue; 144 putprivs(id, quotatype, protoprivs); 145 } 146 exit(0); 147 } 148 if ((tmpfd = mkstemp(tmpfil)) == -1) 149 errx(1, "%s", tmpfil); 150 if (tflag) { 151 protoprivs = getprivs(0, quotatype); 152 if (writetimes(protoprivs, tmpfd, quotatype) == 0) { 153 unlink(tmpfil); 154 exit(1); 155 } 156 if (editit(tmpfil) == -1) { 157 int saved_errno = errno; 158 unlink(tmpfil); 159 errc(1, saved_errno, "error starting editor"); 160 } 161 if (readtimes(protoprivs, tmpfd)) 162 putprivs(0, quotatype, protoprivs); 163 freeprivs(protoprivs); 164 unlink(tmpfil); 165 exit(0); 166 } 167 for ( ; argc > 0; argc--, argv++) { 168 if (getentry(*argv, quotatype, &id) == -1) 169 continue; 170 curprivs = getprivs(id, quotatype); 171 if (writeprivs(curprivs, tmpfd, *argv, quotatype) == 0) 172 continue; 173 if (editit(tmpfil) == -1) { 174 warn("error starting editor"); 175 continue; 176 } 177 if (readprivs(curprivs, tmpfd)) 178 putprivs(id, quotatype, curprivs); 179 freeprivs(curprivs); 180 } 181 close(tmpfd); 182 unlink(tmpfil); 183 exit(0); 184 } 185 186 /* 187 * This routine converts a name for a particular quota type to 188 * an identifier. This routine must agree with the kernel routine 189 * getinoquota as to the interpretation of quota types. 190 */ 191 int 192 getentry(char *name, int quotatype, u_int *idp) 193 { 194 struct passwd *pw; 195 struct group *gr; 196 u_int id; 197 198 switch(quotatype) { 199 case USRQUOTA: 200 if ((pw = getpwnam(name))) { 201 *idp = pw->pw_uid; 202 return 0; 203 } else if (alldigits(name)) { 204 if ((id = strtoul(name, NULL, 10)) <= UID_MAX) { 205 *idp = id; 206 return 0; 207 } 208 } 209 warnx("%s: no such user", name); 210 break; 211 case GRPQUOTA: 212 if ((gr = getgrnam(name))) { 213 *idp = gr->gr_gid; 214 return 0; 215 } else if (alldigits(name)) { 216 if ((id = strtoul(name, NULL, 10)) <= GID_MAX) { 217 *idp = id; 218 return (0); 219 } 220 } 221 warnx("%s: no such group", name); 222 break; 223 default: 224 warnx("%d: unknown quota type", quotatype); 225 break; 226 } 227 sleep(1); 228 return(-1); 229 } 230 231 /* 232 * Collect the requested quota information. 233 */ 234 struct quotause * 235 getprivs(u_int id, int quotatype) 236 { 237 struct fstab *fs; 238 struct quotause *qup, *quptail; 239 struct quotause *quphead; 240 int qcmd, qupsize, fd; 241 u_int mid; 242 char *qfpathname; 243 static int warned = 0; 244 size_t qfpathnamelen; 245 246 setfsent(); 247 quphead = NULL; 248 qcmd = QCMD(Q_GETQUOTA, quotatype); 249 while ((fs = getfsent())) { 250 if (strcmp(fs->fs_vfstype, "ffs") && 251 strcmp(fs->fs_vfstype, "ufs") && 252 strcmp(fs->fs_vfstype, "mfs")) 253 continue; 254 if (!hasquota(fs, quotatype, &qfpathname)) 255 continue; 256 qfpathnamelen = strlen(qfpathname); 257 qupsize = sizeof(*qup) + qfpathnamelen; 258 if ((qup = (struct quotause *)malloc(qupsize)) == NULL) 259 errx(2, "out of memory"); 260 if (quotactl(fs->fs_file, qcmd, id, (char *)&qup->dqblk) != 0) { 261 if (errno == EOPNOTSUPP && !warned) { 262 warned++; 263 (void)fprintf(stderr, "Warning: %s\n", 264 "Quotas are not compiled into this kernel"); 265 sleep(3); 266 } 267 if (getentry(quotagroup, GRPQUOTA, &mid) == -1) { 268 warned++; 269 (void)fprintf(stderr, "Warning: " 270 "group %s not known, skipping %s\n", 271 quotagroup, fs->fs_file); 272 } 273 if ((fd = open(qfpathname, O_RDONLY)) < 0) { 274 fd = open(qfpathname, O_RDWR|O_CREAT, 0640); 275 if (fd < 0 && errno != ENOENT) { 276 perror(qfpathname); 277 free(qup); 278 continue; 279 } 280 (void)fprintf(stderr, "Creating quota file %s\n", 281 qfpathname); 282 sleep(3); 283 (void)fchown(fd, getuid(), mid); 284 (void)fchmod(fd, 0640); 285 } 286 lseek(fd, (off_t)(id * sizeof(struct dqblk)), SEEK_SET); 287 switch (read(fd, &qup->dqblk, sizeof(struct dqblk))) { 288 case 0: /* EOF */ 289 /* 290 * Convert implicit 0 quota (EOF) 291 * into an explicit one (zero'ed dqblk) 292 */ 293 bzero((caddr_t)&qup->dqblk, 294 sizeof(struct dqblk)); 295 break; 296 297 case sizeof(struct dqblk): /* OK */ 298 break; 299 300 default: /* ERROR */ 301 warn("read error in %s", qfpathname); 302 close(fd); 303 free(qup); 304 continue; 305 } 306 close(fd); 307 } 308 strlcpy(qup->qfname, qfpathname, qfpathnamelen + 1); 309 strlcpy(qup->fsname, fs->fs_file, sizeof qup->fsname); 310 if (quphead == NULL) 311 quphead = qup; 312 else 313 quptail->next = qup; 314 quptail = qup; 315 qup->next = NULL; 316 } 317 endfsent(); 318 return(quphead); 319 } 320 321 /* 322 * Store the requested quota information. 323 */ 324 void 325 putprivs(long id, int quotatype, struct quotause *quplist) 326 { 327 struct quotause *qup; 328 int qcmd, fd; 329 330 qcmd = QCMD(Q_SETQUOTA, quotatype); 331 for (qup = quplist; qup; qup = qup->next) { 332 if (quotactl(qup->fsname, qcmd, id, (char *)&qup->dqblk) == 0) 333 continue; 334 if ((fd = open(qup->qfname, O_WRONLY)) < 0) { 335 perror(qup->qfname); 336 } else { 337 lseek(fd, (off_t)(id * sizeof (struct dqblk)), SEEK_SET); 338 if (write(fd, &qup->dqblk, sizeof (struct dqblk)) != 339 sizeof (struct dqblk)) 340 warn("%s", qup->qfname); 341 close(fd); 342 } 343 } 344 } 345 346 /* 347 * Execute an editor on the specified pathname, which is interpreted 348 * from the shell. This means flags may be included. 349 * 350 * Returns -1 on error, or the exit value on success. 351 */ 352 int 353 editit(const char *pathname) 354 { 355 char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p; 356 sig_t sighup, sigint, sigquit, sigchld; 357 pid_t pid; 358 int saved_errno, st, ret = -1; 359 360 ed = getenv("VISUAL"); 361 if (ed == NULL || ed[0] == '\0') 362 ed = getenv("EDITOR"); 363 if (ed == NULL || ed[0] == '\0') 364 ed = _PATH_VI; 365 if (asprintf(&p, "%s %s", ed, pathname) == -1) 366 return (-1); 367 argp[2] = p; 368 369 sighup = signal(SIGHUP, SIG_IGN); 370 sigint = signal(SIGINT, SIG_IGN); 371 sigquit = signal(SIGQUIT, SIG_IGN); 372 sigchld = signal(SIGCHLD, SIG_DFL); 373 if ((pid = fork()) == -1) 374 goto fail; 375 if (pid == 0) { 376 execv(_PATH_BSHELL, argp); 377 _exit(127); 378 } 379 while (waitpid(pid, &st, 0) == -1) 380 if (errno != EINTR) 381 goto fail; 382 if (!WIFEXITED(st)) 383 errno = EINTR; 384 else 385 ret = WEXITSTATUS(st); 386 387 fail: 388 saved_errno = errno; 389 (void)signal(SIGHUP, sighup); 390 (void)signal(SIGINT, sigint); 391 (void)signal(SIGQUIT, sigquit); 392 (void)signal(SIGCHLD, sigchld); 393 free(p); 394 errno = saved_errno; 395 return (ret); 396 } 397 398 /* 399 * Convert a quotause list to an ASCII file. 400 */ 401 int 402 writeprivs(struct quotause *quplist, int outfd, char *name, int quotatype) 403 { 404 struct quotause *qup; 405 FILE *fp; 406 407 ftruncate(outfd, 0); 408 lseek(outfd, 0, SEEK_SET); 409 if ((fp = fdopen(dup(outfd), "w")) == NULL) 410 err(1, "%s", tmpfil); 411 (void)fprintf(fp, "Quotas for %s %s:\n", qfextension[quotatype], name); 412 for (qup = quplist; qup; qup = qup->next) { 413 (void)fprintf(fp, "%s: %s %d, limits (soft = %d, hard = %d)\n", 414 qup->fsname, "KBytes in use:", 415 (int)(dbtob((u_quad_t)qup->dqblk.dqb_curblocks) / 1024), 416 (int)(dbtob((u_quad_t)qup->dqblk.dqb_bsoftlimit) / 1024), 417 (int)(dbtob((u_quad_t)qup->dqblk.dqb_bhardlimit) / 1024)); 418 (void)fprintf(fp, "%s %d, limits (soft = %d, hard = %d)\n", 419 "\tinodes in use:", qup->dqblk.dqb_curinodes, 420 qup->dqblk.dqb_isoftlimit, qup->dqblk.dqb_ihardlimit); 421 } 422 fclose(fp); 423 return(1); 424 } 425 426 /* 427 * Merge changes to an ASCII file into a quotause list. 428 */ 429 int 430 readprivs(struct quotause *quplist, int infd) 431 { 432 struct quotause *qup; 433 FILE *fp; 434 int cnt; 435 char *cp; 436 struct dqblk dqblk; 437 char *fsp, line1[BUFSIZ], line2[BUFSIZ]; 438 439 lseek(infd, 0, SEEK_SET); 440 fp = fdopen(dup(infd), "r"); 441 if (fp == NULL) { 442 warnx("can't re-read temp file!!"); 443 return(0); 444 } 445 /* 446 * Discard title line, then read pairs of lines to process. 447 */ 448 (void)fgets(line1, sizeof (line1), fp); 449 while (fgets(line1, sizeof (line1), fp) != NULL && 450 fgets(line2, sizeof (line2), fp) != NULL) { 451 if ((fsp = strtok(line1, " \t:")) == NULL) { 452 warnx("%s: bad format", line1); 453 return(0); 454 } 455 if ((cp = strtok(NULL, "\n")) == NULL) { 456 warnx("%s: %s: bad format", fsp, &fsp[strlen(fsp) + 1]); 457 return(0); 458 } 459 cnt = sscanf(cp, 460 " KBytes in use: %d, limits (soft = %d, hard = %d)", 461 &dqblk.dqb_curblocks, &dqblk.dqb_bsoftlimit, 462 &dqblk.dqb_bhardlimit); 463 if (cnt != 3) { 464 warnx("%s:%s: bad format", fsp, cp); 465 return(0); 466 } 467 dqblk.dqb_curblocks = btodb((u_quad_t) 468 dqblk.dqb_curblocks * 1024); 469 dqblk.dqb_bsoftlimit = btodb((u_quad_t) 470 dqblk.dqb_bsoftlimit * 1024); 471 dqblk.dqb_bhardlimit = btodb((u_quad_t) 472 dqblk.dqb_bhardlimit * 1024); 473 if ((cp = strtok(line2, "\n")) == NULL) { 474 warnx("%s: %s: bad format", fsp, line2); 475 return(0); 476 } 477 cnt = sscanf(cp, 478 "\tinodes in use: %d, limits (soft = %d, hard = %d)", 479 &dqblk.dqb_curinodes, &dqblk.dqb_isoftlimit, 480 &dqblk.dqb_ihardlimit); 481 if (cnt != 3) { 482 warnx("%s: %s: bad format", fsp, line2); 483 return(0); 484 } 485 for (qup = quplist; qup; qup = qup->next) { 486 if (strcmp(fsp, qup->fsname)) 487 continue; 488 /* 489 * Cause time limit to be reset when the quota 490 * is next used if previously had no soft limit 491 * or were under it, but now have a soft limit 492 * and are over it. 493 */ 494 if (dqblk.dqb_bsoftlimit && 495 qup->dqblk.dqb_curblocks >= dqblk.dqb_bsoftlimit && 496 (qup->dqblk.dqb_bsoftlimit == 0 || 497 qup->dqblk.dqb_curblocks < 498 qup->dqblk.dqb_bsoftlimit)) 499 qup->dqblk.dqb_btime = 0; 500 if (dqblk.dqb_isoftlimit && 501 qup->dqblk.dqb_curinodes >= dqblk.dqb_isoftlimit && 502 (qup->dqblk.dqb_isoftlimit == 0 || 503 qup->dqblk.dqb_curinodes < 504 qup->dqblk.dqb_isoftlimit)) 505 qup->dqblk.dqb_itime = 0; 506 qup->dqblk.dqb_bsoftlimit = dqblk.dqb_bsoftlimit; 507 qup->dqblk.dqb_bhardlimit = dqblk.dqb_bhardlimit; 508 qup->dqblk.dqb_isoftlimit = dqblk.dqb_isoftlimit; 509 qup->dqblk.dqb_ihardlimit = dqblk.dqb_ihardlimit; 510 qup->flags |= FOUND; 511 if (dqblk.dqb_curblocks == qup->dqblk.dqb_curblocks && 512 dqblk.dqb_curinodes == qup->dqblk.dqb_curinodes) 513 break; 514 warnx("%s: cannot change current allocation", fsp); 515 break; 516 } 517 } 518 fclose(fp); 519 /* 520 * Disable quotas for any filesystems that have not been found. 521 */ 522 for (qup = quplist; qup; qup = qup->next) { 523 if (qup->flags & FOUND) { 524 qup->flags &= ~FOUND; 525 continue; 526 } 527 qup->dqblk.dqb_bsoftlimit = 0; 528 qup->dqblk.dqb_bhardlimit = 0; 529 qup->dqblk.dqb_isoftlimit = 0; 530 qup->dqblk.dqb_ihardlimit = 0; 531 } 532 return(1); 533 } 534 535 /* 536 * Convert a quotause list to an ASCII file of grace times. 537 */ 538 int 539 writetimes(struct quotause *quplist, int outfd, int quotatype) 540 { 541 struct quotause *qup; 542 FILE *fp; 543 544 ftruncate(outfd, 0); 545 lseek(outfd, 0, SEEK_SET); 546 if ((fp = fdopen(dup(outfd), "w")) == NULL) 547 err(1, "%s", tmpfil); 548 (void)fprintf(fp, 549 "Time units may be: days, hours, minutes, or seconds\n"); 550 (void)fprintf(fp, 551 "Grace period before enforcing soft limits for %ss:\n", 552 qfextension[quotatype]); 553 for (qup = quplist; qup; qup = qup->next) { 554 (void)fprintf(fp, "%s: block grace period: %s, ", 555 qup->fsname, cvtstoa(qup->dqblk.dqb_btime)); 556 (void)fprintf(fp, "file grace period: %s\n", 557 cvtstoa(qup->dqblk.dqb_itime)); 558 } 559 fclose(fp); 560 return(1); 561 } 562 563 /* 564 * Merge changes of grace times in an ASCII file into a quotause list. 565 */ 566 int 567 readtimes(struct quotause *quplist, int infd) 568 { 569 struct quotause *qup; 570 FILE *fp; 571 int cnt; 572 char *cp; 573 long long itime, btime; 574 time_t iseconds, bseconds; 575 char *fsp, bunits[10], iunits[10], line1[BUFSIZ]; 576 577 lseek(infd, 0, SEEK_SET); 578 fp = fdopen(dup(infd), "r"); 579 if (fp == NULL) { 580 warnx("can't re-read temp file!!"); 581 return(0); 582 } 583 /* 584 * Discard two title lines, then read lines to process. 585 */ 586 (void)fgets(line1, sizeof (line1), fp); 587 (void)fgets(line1, sizeof (line1), fp); 588 while (fgets(line1, sizeof (line1), fp) != NULL) { 589 if ((fsp = strtok(line1, " \t:")) == NULL) { 590 warnx("%s: bad format", line1); 591 return(0); 592 } 593 if ((cp = strtok(NULL, "\n")) == NULL) { 594 warnx("%s: %s: bad format", fsp, 595 &fsp[strlen(fsp) + 1]); 596 return(0); 597 } 598 cnt = sscanf(cp, 599 " block grace period: %lld %9s file grace period: %lld %9s", 600 &btime, bunits, &itime, iunits); 601 if (cnt != 4) { 602 warnx("%s:%s: bad format", fsp, cp); 603 return(0); 604 } 605 if (cvtatos(btime, bunits, &bseconds) == 0) 606 return(0); 607 if (cvtatos(itime, iunits, &iseconds) == 0) 608 return(0); 609 for (qup = quplist; qup; qup = qup->next) { 610 if (strcmp(fsp, qup->fsname)) 611 continue; 612 qup->dqblk.dqb_btime = bseconds; 613 qup->dqblk.dqb_itime = iseconds; 614 qup->flags |= FOUND; 615 break; 616 } 617 } 618 fclose(fp); 619 /* 620 * reset default grace periods for any filesystems 621 * that have not been found. 622 */ 623 for (qup = quplist; qup; qup = qup->next) { 624 if (qup->flags & FOUND) { 625 qup->flags &= ~FOUND; 626 continue; 627 } 628 qup->dqblk.dqb_btime = 0; 629 qup->dqblk.dqb_itime = 0; 630 } 631 return(1); 632 } 633 634 /* 635 * Convert seconds to ASCII times. 636 */ 637 char * 638 cvtstoa(time_t time) 639 { 640 static char buf[20]; 641 642 if (time % (24 * 60 * 60) == 0) { 643 time /= 24 * 60 * 60; 644 (void)snprintf(buf, sizeof buf, "%lld day%s", (long long)time, 645 time == 1 ? "" : "s"); 646 } else if (time % (60 * 60) == 0) { 647 time /= 60 * 60; 648 (void)snprintf(buf, sizeof buf, "%lld hour%s", (long long)time, 649 time == 1 ? "" : "s"); 650 } else if (time % 60 == 0) { 651 time /= 60; 652 (void)snprintf(buf, sizeof buf, "%lld minute%s", 653 (long long)time, time == 1 ? "" : "s"); 654 } else 655 (void)snprintf(buf, sizeof buf, "%lld second%s", 656 (long long)time, time == 1 ? "" : "s"); 657 return(buf); 658 } 659 660 /* 661 * Convert ASCII input times to seconds. 662 */ 663 int 664 cvtatos(long long time, char *units, time_t *seconds) 665 { 666 667 if (bcmp(units, "second", 6) == 0) 668 *seconds = time; 669 else if (bcmp(units, "minute", 6) == 0) 670 *seconds = time * 60; 671 else if (bcmp(units, "hour", 4) == 0) 672 *seconds = time * 60 * 60; 673 else if (bcmp(units, "day", 3) == 0) 674 *seconds = time * 24 * 60 * 60; 675 else { 676 (void)printf("%s: bad units, specify %s\n", units, 677 "days, hours, minutes, or seconds"); 678 return(0); 679 } 680 return(1); 681 } 682 683 /* 684 * Free a list of quotause structures. 685 */ 686 void 687 freeprivs(struct quotause *quplist) 688 { 689 struct quotause *qup, *nextqup; 690 691 for (qup = quplist; qup; qup = nextqup) { 692 nextqup = qup->next; 693 free(qup); 694 } 695 } 696 697 /* 698 * Check whether a string is completely composed of digits. 699 */ 700 int 701 alldigits(char *s) 702 { 703 int c; 704 705 c = (unsigned char)*s++; 706 do { 707 if (!isdigit(c)) 708 return(0); 709 } while ((c = (unsigned char)*s++)); 710 return(1); 711 } 712 713 /* 714 * Check to see if a particular quota is to be enabled. 715 */ 716 int 717 hasquota(struct fstab *fs, int type, char **qfnamep) 718 { 719 char *opt; 720 char *cp; 721 static char initname, usrname[100], grpname[100]; 722 static char buf[BUFSIZ]; 723 724 if (!initname) { 725 (void)snprintf(usrname, sizeof usrname, "%s%s", 726 qfextension[USRQUOTA], qfname); 727 (void)snprintf(grpname, sizeof grpname, "%s%s", 728 qfextension[GRPQUOTA], qfname); 729 initname = 1; 730 } 731 strlcpy(buf, fs->fs_mntops, sizeof buf); 732 for (opt = strtok(buf, ","); opt; opt = strtok(NULL, ",")) { 733 if ((cp = strchr(opt, '='))) 734 *cp++ = '\0'; 735 if (type == USRQUOTA && strcmp(opt, usrname) == 0) 736 break; 737 if (type == GRPQUOTA && strcmp(opt, grpname) == 0) 738 break; 739 } 740 if (!opt) 741 return(0); 742 if (cp) { 743 *qfnamep = cp; 744 return(1); 745 } 746 (void)snprintf(buf, sizeof buf, "%s/%s.%s", 747 fs->fs_file, qfname, qfextension[type]); 748 *qfnamep = buf; 749 return(1); 750 } 751