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