xref: /titanic_41/usr/src/lib/libnsl/dial/sysfiles.c (revision 6a634c9dca3093f3922e4b7ab826d7bdf17bf78e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 1989, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved	*/
28 
29 #include "mt.h"
30 #include "uucp.h"
31 
32 #include <unistd.h>
33 #include <string.h>
34 #include "sysfiles.h"
35 #include <sys/stropts.h>
36 
37 /*
38  * manage systems files (Systems, Devices, and Dialcodes families).
39  *
40  * also manage new file Devconfig, allows per-device setup.
41  * present use is to specify what streams modules to push/pop for
42  * AT&T TLI/streams network.
43  *
44  * TODO:
45  *	call bsfix()?
46  *	combine the 3 versions of everything (sys, dev, and dial) into one.
47  *	allow arbitrary classes of service.
48  *	need verifysys() for uucheck.
49  *	nameserver interface?
50  *	pass sysname (or 0) to getsysline().  (might want reg. exp. or
51  *		NS processing)
52  */
53 
54 /* private variables */
55 static void tokenize(void);
56 static void nameparse(void);
57 static void setfile(char **, char *);
58 static void setioctl(char **, char *);
59 static void scansys(const char *);
60 static void scancfg(char *, char *);
61 static void setconfig(void);
62 static int namematch(const char *label, char *line, const char *name);
63 static int nextdialers(void);
64 static int nextdevices(void);
65 static int nextsystems(void);
66 static int getaline(FILE *, char *);
67 
68 /* pointer arrays might be dynamically allocated */
69 static char *Systems[64];	/* list of Systems files */
70 static char *Devices[64];	/* list of Devices files */
71 static char *Dialers[64];	/* list of Dialers files */
72 static char *Pops[64];		/* list of STREAMS modules to be popped */
73 static char *Pushes[64];	/* list of STREAMS modules to be pushed */
74 
75 static int nsystems;		/* index into list of Systems files */
76 static int ndevices;		/* index into list of Devices files */
77 static int ndialers;		/* index into list of Dialers files */
78 static int npops;		/* index into list of STREAMS modules */
79 							/* to be popped */
80 static int npushes;		/* index into list of STREAMS modules */
81 							/* to be pushed */
82 
83 static unsigned connecttime, expecttime;
84 
85 static FILE *fsystems;
86 static FILE *fdevices;
87 static FILE *fdialers;
88 
89 /* this might be dynamically allocated */
90 #define	NTOKENS 16
91 static char *tokens[NTOKENS], **tokptr;
92 
93 /* export these */
94 static void setservice(const char *service);
95 static void sysreset(void);
96 static void devreset(void);
97 static void dialreset(void);
98 static void setdevcfg(char *, char *);
99 static void setservice(const char *);
100 
101 /* import these */
102 extern char *strsave(const char *);
103 static int eaccess(char *, mode_t);
104 
105 /*
106  * setservice init's Systems, Devices, Dialers lists from Sysfiles
107  */
108 static void
setservice(const char * service)109 setservice(const char *service)
110 {
111 	setconfig();
112 	scansys(service);
113 }
114 
115 /*
116  * setdevcfg init's Pops, Pushes lists from Devconfig
117  */
118 
119 static void
setdevcfg(char * service,char * device)120 setdevcfg(char *service, char *device)
121 {
122 	scancfg(service, device);
123 }
124 
125 /*	administrative files access */
126 static int
sysaccess(int type)127 sysaccess(int type)
128 {
129 	char errformat[BUFSIZ];
130 
131 	switch (type) {
132 	case ACCESS_SYSTEMS:
133 		return (access(Systems[nsystems], R_OK));
134 	case ACCESS_DEVICES:
135 		return (access(Devices[ndevices], R_OK));
136 	case ACCESS_DIALERS:
137 		return (access(Dialers[ndialers], R_OK));
138 	case EACCESS_SYSTEMS:
139 		return (eaccess(Systems[nsystems], R_OK));
140 	case EACCESS_DEVICES:
141 		return (eaccess(Devices[ndevices], R_OK));
142 	case EACCESS_DIALERS:
143 		return (eaccess(Dialers[ndialers], R_OK));
144 	}
145 	(void) sprintf(errformat, "bad access type %d", type);
146 	logent(errformat, "sysaccess");
147 	return (FAIL);
148 }
149 
150 
151 /*
152  * read Sysfiles, set up lists of Systems/Devices/Dialers file names.
153  * allow multiple entries for a given service, allow a service
154  * type to describe resources more than once, e.g., systems=foo:baz systems=bar.
155  */
156 static void
scansys(const char * service)157 scansys(const char *service)
158 {	FILE *f;
159 	char *tok, buf[BUFSIZ];
160 	char **tptr;
161 
162 	/*
163 	 * Release and Initialize previously allocated memory
164 	 * for Systems, Devices and Dialers.
165 	 */
166 	nsystems = 0;
167 	tptr = Systems;
168 	while (*tptr) {
169 		free(*tptr);
170 		*tptr = NULL;
171 		tptr++;
172 	}
173 
174 	ndevices = 0;
175 	tptr = Devices;
176 	while (*tptr) {
177 		free(*tptr);
178 		*tptr = NULL;
179 		tptr++;
180 	}
181 
182 	ndialers = 0;
183 	tptr = Dialers;
184 	while (*tptr) {
185 		free(*tptr);
186 		*tptr = NULL;
187 		tptr++;
188 	}
189 
190 	if ((f = fopen(SYSFILES, "rF")) != 0) {
191 		while (getaline(f, buf) > 0) {
192 			/* got a (logical) line from Sysfiles */
193 			/* strtok's of this buf continue in tokenize() */
194 			tok = strtok(buf, " \t");
195 			if (namematch("service=", tok, service)) {
196 				tokenize();
197 				nameparse();
198 			}
199 		}
200 		(void) fclose(f);
201 	}
202 
203 	/* if didn't find entries in Sysfiles, use defaults */
204 	if (Systems[0] == NULL) {
205 		Systems[0] = strsave(SYSTEMS);
206 		ASSERT(Systems[0] != NULL, "Ct_ALLOCATE", "scansys: Systems",
207 									0);
208 		Systems[1] = NULL;
209 	}
210 	if (Devices[0] == NULL) {
211 		Devices[0] = strsave(DEVICES);
212 		ASSERT(Devices[0] != NULL, "Ct_ALLOCATE", "scansys: Devices",
213 									0);
214 		Devices[1] = NULL;
215 	}
216 	if (Dialers[0] == NULL) {
217 		Dialers[0] = strsave(DIALERS);
218 		ASSERT(Dialers[0] != NULL, "Ct_ALLOCATE", "scansys: Dialers",
219 									0);
220 		Dialers[1] = NULL;
221 	}
222 }
223 
224 
225 /*
226  * read Devconfig.  allow multiple entries for a given service, allow a service
227  * type to describe resources more than once, e.g., push=foo:baz push=bar.
228  */
229 static void
scancfg(char * service,char * device)230 scancfg(char *service, char *device)
231 {	FILE *f;
232 	char *tok, buf[BUFSIZ];
233 
234 	/* (re)initialize device-specific information */
235 	npops = npushes = 0;
236 	Pops[0] = Pushes[0] = NULL;
237 	connecttime = CONNECTTIME;
238 	expecttime = EXPECTTIME;
239 
240 	if ((f = fopen(DEVCONFIG, "rF")) != 0) {
241 		while (getaline(f, buf) > 0) {
242 			/* got a (logical) line from Devconfig */
243 			/* strtok's of this buf continue in tokenize() */
244 			tok = strtok(buf, " \t");
245 			if (namematch("service=", tok, service)) {
246 				tok = strtok((char *)0, " \t");
247 				if (namematch("device=", tok, device)) {
248 					tokenize();
249 					nameparse();
250 				}
251 			}
252 		}
253 		(void) fclose(f);
254 	}
255 	return;
256 
257 }
258 
259 /*
260  *  given a file pointer and buffer, construct logical line in buffer
261  *  (i.e., concatenate lines ending in '\').  return length of line
262  *  ASSUMES that buffer is BUFSIZ long!
263  */
264 
265 static int
getaline(FILE * f,char * line)266 getaline(FILE *f, char *line)
267 {	char *lptr, *lend;
268 
269 	lptr = line;
270 	while (fgets(lptr, (line + BUFSIZ) - lptr, f) != NULL) {
271 		lend = lptr + strlen(lptr);
272 		if (lend == lptr || lend[-1] != '\n')
273 			/* empty buf or line too long! */
274 			break;
275 		*--lend = '\0'; /* lop off ending '\n' */
276 		if (lend == line) /* empty line - ignore */
277 			continue;
278 		lptr = lend;
279 		if (lend[-1] != '\\')
280 			break;
281 		/* continuation */
282 		lend[-1] = ' ';
283 	}
284 	return (lptr - line);
285 }
286 
287 /*
288  * given a label (e.g., "service=", "device="), a name ("cu", "uucico"),
289  *  and a line:  if line begins with the label and if the name appears
290  * in a colon-separated list of names following the label, return true;
291  * else return false
292  */
293 static int
namematch(const char * label,char * line,const char * name)294 namematch(const char *label, char *line, const char *name)
295 {
296 	char *lend;
297 
298 	if (strncmp(label, line, strlen(label)) != SAME)
299 		return (FALSE);	/* probably a comment line */
300 	line += strlen(label);
301 	if (*line == '\0')
302 		return (FALSE);
303 	/*
304 	 * can't use strtok() in the following because scansys(),
305 	 * scancfg() do an initializing call to strtok() before
306 	 * coming here and then CONTINUE calling strtok() in tokenize(),
307 	 * after returning from namematch().
308 	 */
309 	while ((lend = strchr(line, ':')) != NULL) {
310 		*lend = '\0';
311 		if (strcmp(line, name) == SAME)
312 			return (TRUE);
313 		line = lend+1;
314 	}
315 	return (strcmp(line, name) == SAME);
316 }
317 
318 /*
319  * tokenize() continues pulling tokens out of a buffer -- the
320  * initializing call to strtok must have been made before calling
321  * tokenize() -- and starts stuffing 'em into tokptr.
322  */
323 static void
tokenize(void)324 tokenize(void)
325 {
326 	char *tok;
327 
328 	tokptr = tokens;
329 	while ((tok = strtok(NULL, " \t")) != NULL) {
330 		*tokptr++ = tok;
331 		if (tokptr - tokens >= NTOKENS)
332 			break;
333 	}
334 	*tokptr = NULL;
335 }
336 
337 /*
338  * look at top token in array: should be line of the form
339  *	name=item1:item2:item3...
340  * if name is one we recognize, then call set[file|ioctl] to set up
341  * corresponding list.  otherwise, log bad name.
342  */
343 static void
nameparse(void)344 nameparse(void)
345 {
346 	char **line, *equals;
347 	int temp;
348 
349 #define	setuint(a, b, c) a = (((temp = atoi(b)) <= 0) ? (c) : temp)
350 
351 	for (line = tokens; (line - tokens) < NTOKENS && *line; line++) {
352 		equals = strchr(*line, '=');
353 		if (equals == NULL)
354 			continue;	/* may be meaningful someday? */
355 		*equals = '\0';
356 		/* ignore entry with empty rhs */
357 		if (*++equals == '\0')
358 			continue;
359 		if (strcmp(*line, "systems") == SAME)
360 			setfile(Systems, equals);
361 		else if (strcmp(*line, "devices") == SAME)
362 			setfile(Devices, equals);
363 		else if (strcmp(*line, "dialers") == SAME)
364 			setfile(Dialers, equals);
365 		else if (strcmp(*line, "pop") == SAME)
366 			setioctl(Pops, equals);
367 		else if (strcmp(*line, "push") == SAME)
368 			setioctl(Pushes, equals);
369 		else if (strcmp(*line, "connecttime") == SAME)
370 			setuint(connecttime, equals, CONNECTTIME);
371 		else if (strcmp(*line, "expecttime") == SAME)
372 			setuint(expecttime, equals, EXPECTTIME);
373 		else if (strcmp(*line, "msgtime") == SAME)
374 			continue;
375 		else {
376 			char errformat[BUFSIZ];
377 
378 			(void) snprintf(errformat, sizeof (errformat),
379 			    "unrecognized label %s", *line);
380 			logent(errformat, "Sysfiles|Devconfig");
381 		}
382 	}
383 }
384 
385 /*
386  * given the list for a particular type (systems, devices,...)
387  * and a line of colon-separated files, add 'em to list
388  */
389 
390 static void
setfile(char ** type,char * line)391 setfile(char **type, char *line)
392 {
393 	char **tptr, *tok;
394 	char expandpath[BUFSIZ];
395 
396 	if (*line == 0)
397 		return;
398 	tptr = type;
399 	while (*tptr)		/* skip over existing entries to */
400 		tptr++;		/* concatenate multiple entries */
401 
402 	for (tok = strtok(line, ":"); tok != NULL; tok = strtok(NULL, ":")) {
403 		expandpath[0] = '\0';
404 		if (*tok != '/')
405 			/* by default, file names are relative to SYSDIR */
406 			(void) snprintf(expandpath, sizeof (expandpath),
407 			    "%s/", SYSDIR);
408 		(void) strcat(expandpath, tok);
409 		if (eaccess(expandpath, R_OK) != 0)
410 			/* if we can't read it, no point in adding to list */
411 			continue;
412 		*tptr = strsave(expandpath);
413 		ASSERT(*tptr != NULL, "Ct_ALLOCATE", "setfile: tptr", 0);
414 		tptr++;
415 	}
416 	*tptr = NULL;
417 }
418 
419 /*
420  * given the list for a particular ioctl (push, pop)
421  * and a line of colon-separated modules, add 'em to list
422  */
423 
424 static void
setioctl(char ** type,char * line)425 setioctl(char **type, char *line)
426 {
427 	char **tptr, *tok;
428 
429 	if (*line == 0)
430 		return;
431 	tptr = type;
432 	while (*tptr)		/* skip over existing entries to */
433 		tptr++;		/* concatenate multiple entries */
434 	for (tok = strtok(line, ":"); tok != NULL; tok = strtok(NULL, ":")) {
435 		*tptr = strsave(tok);
436 		ASSERT(*tptr != NULL, "Ct_ALLOCATE", "setioctl: tptr", 0);
437 		tptr++;
438 	}
439 }
440 
441 /*
442  * reset Systems files
443  */
444 static void
sysreset(void)445 sysreset(void)
446 {
447 	if (fsystems)
448 		(void) fclose(fsystems);
449 	fsystems = NULL;
450 	nsystems = 0;
451 	devreset();
452 }
453 
454 /*
455  * reset Devices files
456  */
457 static void
devreset(void)458 devreset(void)
459 {
460 	if (fdevices)
461 		(void) fclose(fdevices);
462 	fdevices = NULL;
463 	ndevices = 0;
464 	dialreset();
465 }
466 
467 /*
468  * reset Dialers files
469  */
470 static void
dialreset(void)471 dialreset(void)
472 {
473 	if (fdialers)
474 		(void) fclose(fdialers);
475 	fdialers = NULL;
476 	ndialers = 0;
477 }
478 
479 /*
480  * get next line from Systems file
481  * return TRUE if successful, FALSE if not
482  */
483 static int
getsysline(char * buf,int len)484 getsysline(char *buf, int len)
485 {
486 	if (Systems[0] == NULL)
487 		/* not initialized via setservice() - use default */
488 		setservice("uucico");
489 
490 	/* initialize devices and dialers whenever a new line is read */
491 	/* from systems */
492 	devreset();
493 	if (fsystems == NULL)
494 		if (nextsystems() == FALSE)
495 			return (FALSE);
496 
497 	for (;;) {
498 		while (fgets(buf, len, fsystems) != NULL)
499 			if ((*buf != '#') && (*buf != ' ') &&
500 			    (*buf != '\t') && (*buf != '\n'))
501 			return (TRUE);
502 		if (nextsystems() == FALSE)
503 			return (FALSE);
504 	}
505 }
506 
507 /*
508  * move to next systems file.  return TRUE if successful, FALSE if not
509  */
510 static int
nextsystems(void)511 nextsystems(void)
512 {
513 	devreset();
514 
515 	if (fsystems != NULL) {
516 		(void) fclose(fsystems);
517 		nsystems++;
518 	} else {
519 		nsystems = 0;
520 	}
521 	for (; Systems[nsystems] != NULL; nsystems++)
522 		if ((fsystems = fopen(Systems[nsystems], "rF")) != NULL)
523 			return (TRUE);
524 	return (FALSE);
525 }
526 
527 /*
528  * get next line from Devices file
529  * return TRUE if successful, FALSE if not
530  */
531 static int
getdevline(char * buf,int len)532 getdevline(char *buf, int len)
533 {
534 	if (Devices[0] == NULL)
535 		/* not initialized via setservice() - use default */
536 		setservice("uucico");
537 
538 	if (fdevices == NULL)
539 		if (nextdevices() == FALSE)
540 			return (FALSE);
541 	for (;;) {
542 		if (fgets(buf, len, fdevices) != NULL)
543 			return (TRUE);
544 		if (nextdevices() == FALSE)
545 			return (FALSE);
546 	}
547 }
548 
549 /*
550  * move to next devices file.  return TRUE if successful, FALSE if not
551  */
552 static int
nextdevices(void)553 nextdevices(void)
554 {
555 	if (fdevices != NULL) {
556 		(void) fclose(fdevices);
557 		ndevices++;
558 	} else {
559 		ndevices = 0;
560 	}
561 	for (; Devices[ndevices] != NULL; ndevices++)
562 		if ((fdevices = fopen(Devices[ndevices], "rF")) != NULL)
563 			return (TRUE);
564 	return (FALSE);
565 }
566 
567 
568 /*
569  * get next line from Dialers file
570  * return TRUE if successful, FALSE if not
571  */
572 
573 static int
getdialline(char * buf,int len)574 getdialline(char *buf, int len)
575 {
576 	if (Dialers[0] == NULL)
577 		/* not initialized via setservice() - use default */
578 		setservice("uucico");
579 
580 	if (fdialers == NULL)
581 		if (nextdialers() == FALSE)
582 			return (FALSE);
583 	for (;;) {
584 		if (fgets(buf, len, fdialers) != NULL)
585 			return (TRUE);
586 		if (nextdialers() == FALSE)
587 			return (FALSE);
588 	}
589 }
590 
591 /*
592  * move to next dialers file.  return TRUE if successful, FALSE if not
593  */
594 static int
nextdialers(void)595 nextdialers(void)
596 {
597 	if (fdialers) {
598 		(void) fclose(fdialers);
599 		ndialers++;
600 	} else {
601 		ndialers = 0;
602 	}
603 
604 	for (; Dialers[ndialers] != NULL; ndialers++)
605 		if ((fdialers = fopen(Dialers[ndialers], "rF")) != NULL)
606 			return (TRUE);
607 	return (FALSE);
608 }
609 
610 /*
611  * get next module to be popped
612  * return TRUE if successful, FALSE if not
613  */
614 static int
getpop(char * buf,size_t len,int * optional)615 getpop(char *buf, size_t len, int *optional)
616 {
617 	int slen;
618 
619 	if (Pops[0] == NULL || Pops[npops] == NULL)
620 		return (FALSE);
621 
622 	/*	if the module name is enclosed in parentheses,	*/
623 	/*	is optional. set flag & strip parens		*/
624 	slen = strlen(Pops[npops]) - 1;
625 	if (Pops[npops][0] == '(' && Pops[npops][slen] == ')') {
626 		*optional = 1;
627 		len = (slen < len ? slen : len);
628 		(void) strncpy(buf, &(Pops[npops++][1]), len);
629 	} else {
630 		*optional = 0;
631 		(void) strncpy(buf, Pops[npops++], len);
632 	}
633 	buf[len-1] = '\0';
634 	return (TRUE);
635 }
636 
637 /*
638  * get next module to be pushed
639  * return TRUE if successful, FALSE if not
640  */
641 static int
getpush(char * buf,size_t len)642 getpush(char *buf, size_t len)
643 {
644 	if (Pushes[0] == NULL || Pushes[npushes] == NULL)
645 		return (FALSE);
646 	(void) strncpy(buf, Pushes[npushes++], len);
647 	return (TRUE);
648 }
649 
650 /*
651  * pop/push requested modules
652  * return TRUE if successful, FALSE if not
653  */
654 static int
pop_push(int fd)655 pop_push(int fd)
656 {
657 	char	strmod[FMNAMESZ], onstream[FMNAMESZ];
658 	int		optional;
659 
660 	/*	check for streams modules to pop	*/
661 	while (getpop(strmod, sizeof (strmod), &optional)) {
662 		DEBUG(5, (optional ?
663 		    (const char *)"pop_push: optionally POPing %s\n" :
664 		    (const char *)"pop_push: POPing %s\n"), strmod);
665 		if (ioctl(fd, I_LOOK, onstream) == -1) {
666 			DEBUG(5, "pop_push: I_LOOK on fd %d failed ", fd);
667 			DEBUG(5, "errno %d\n", errno);
668 			return (FALSE);
669 		}
670 		if (strcmp(strmod, onstream) != SAME) {
671 			if (optional)
672 				continue;
673 			DEBUG(5, "pop_push: I_POP: %s not there\n", strmod);
674 			return (FALSE);
675 		}
676 		if (ioctl(fd, I_POP, 0) == -1) {
677 			DEBUG(5, "pop_push: I_POP on fd %d failed ", fd);
678 			DEBUG(5, "errno %d\n", errno);
679 			return (FALSE);
680 		}
681 	}
682 
683 	/*	check for streams modules to push	*/
684 	while (getpush(strmod, sizeof (strmod))) {
685 		DEBUG(5, "pop_push: PUSHing %s\n", strmod);
686 		if (ioctl(fd, I_PUSH, strmod) == -1) {
687 			DEBUG(5, "pop_push: I_PUSH on fd %d failed ", fd);
688 			DEBUG(5, "errno %d\n", errno);
689 			return (FALSE);
690 		}
691 	}
692 	return (TRUE);
693 }
694 
695 #ifndef SMALL
696 /*
697  *	return name of currently open Systems file
698  */
699 static char *
currsys(void)700 currsys(void)
701 {
702 	return (Systems[nsystems]);
703 }
704 
705 /*
706  *	return name of currently open Devices file
707  */
708 static char *
currdev(void)709 currdev(void)
710 {
711 	return (Devices[ndevices]);
712 }
713 
714 /*
715  *	return name of currently open Dialers file
716  */
717 static char *
currdial(void)718 currdial(void)
719 {
720 	return (Dialers[ndialers]);
721 }
722 #endif
723 
724 /*
725  * set configuration parameters provided in Config file
726  */
727 static void
setconfig(void)728 setconfig(void)
729 {
730 	FILE *f;
731 	char buf[BUFSIZ];
732 	char *tok;
733 	extern char _ProtoCfg[];
734 
735 	if ((f = fopen(CONFIG, "rF")) != 0) {
736 	while (getaline(f, buf) > 0) {
737 		/* got a (logical) line from Config file */
738 		tok = strtok(buf, " \t");
739 		if ((tok != NULL) && (*tok != '#')) {
740 			/* got a token */
741 			/*
742 			 * this probably should be table driven when
743 			 * the list of configurable parameters grows.
744 			 */
745 			if (strncmp("Protocol=", tok, strlen("Protocol=")) ==
746 			    SAME) {
747 				tok += strlen("Protocol=");
748 				if (*tok != '\0') {
749 					if (_ProtoCfg[0] != '\0') {
750 						/*EMPTY*/
751 						DEBUG(7, "Protocol string %s ",
752 						    tok);
753 						DEBUG(7, "overrides %s\n",
754 						    _ProtoCfg);
755 					}
756 					(void) strcpy(_ProtoCfg, tok);
757 				}
758 			} else {
759 				/*EMPTY*/
760 				DEBUG(7, "Unknown configuration parameter %s\n",
761 				    tok);
762 			}
763 		}
764 	}
765 	(void) fclose(f);
766 	}
767 }
768