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