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