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