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