1 /***************************************************************************
2  *  Pinfo is a ncurses based lynx style info documentation browser
3  *
4  *  Copyright (C) 1999  Przemek Borys <pborys@dione.ids.pl>
5  *  Copyright (C) 2005  Bas Zoetekouw <bas@debian.org>
6  *  Copyright (C) 2005  Nathanael Nerode <neroden@gcc.gnu.org>
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of version 2 of the GNU General Public License as
10  *  published by the Free Software Foundation.
11  *
12  *  This program is distributed in the hope that it will be useful, but
13  *  WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  *  USA
21  ***************************************************************************/
22 
23 #include "common_includes.h"
24 
25 typedef struct
26 {
27 	char *suffix;
28 	char *command;
29 }
30 Suffixes;
31 
32 
33 /******************************************************************************
34  * This piece of declarations says what to do with info files stored with      *
35  * different formats/compression methods, before putting them into a temporary *
36  * file. I.e. you don't do anything to plain `.info' suffix; for a `.info.gz'  *
37  * you dump the file through `gunzip -d -c', etc.                              *
38  ******************************************************************************/
39 
40 #define SuffixesNumber 4
41 
42 Suffixes suffixes[SuffixesNumber] =
43 {
44 	{"", 		"cat"},
45 	{".gz",		"gzip -d -q -c"},
46 	{".Z",		"gzip -d -q -c"},
47 	{".bz2",	"bzip2 -d -c"}
48 };
49 
50 /*****************************************************************************/
51 
52 char **infopaths = 0;
53 int infopathcount = 0;
54 
55 int
qsort_cmp(const void * base,const void * compared)56 qsort_cmp(const void *base, const void *compared)
57 {
58 	char *cbase =((TagTable *) base)->nodename;
59 	char *ccompared =((TagTable *) compared)->nodename;
60 	return compare_tag_table_string(cbase, ccompared);
61 }
62 
63 int
matchfile(char ** buf,char * name)64 matchfile(char **buf, char *name)
65 {
66 #define Buf	(*buf)
67 	DIR *dir;
68 	char *bname = basename(name);
69 	struct dirent *dp;
70 	int matched = 0;
71 
72 	/* remove a possible ".info" from the end of the file name
73 	 * we're looking for */
74 	strip_info_suffix(bname);
75 
76 	/* fix the name of the dir */
77 	if (Buf[strlen(Buf)-1]!='/')
78 	{
79 		strcat(Buf,"/");
80 	}
81 	strncat(Buf,name,bname-name);
82 
83 	/* open the directory */
84 	dir = opendir(Buf);
85 	if (dir == NULL)
86 	{
87 		return 0;
88 	}
89 
90 	/* iterate over all files in the directory */
91 	while ((dp = readdir(dir)) != NULL)
92 	{
93 		/* use strcat rather than strdup, because xmalloc handles all
94 		 * malloc errors */
95 		char *filename = xmalloc(strlen(dp->d_name)+1);
96 		char *pagename = xmalloc(strlen(dp->d_name)+1);
97 		strcat(filename, dp->d_name);
98 		strcat(pagename, dp->d_name);
99 
100 		/* strip suffixes (so "gcc.info.gz" -> "gcc") */
101 		strip_compression_suffix(pagename);
102 		strip_info_suffix(pagename);
103 
104 		/* strip compression suffix from returned filename
105 		 * decompresison and type matching will happen later
106 		 * (sigh)
107 		 */
108 		strip_compression_suffix(filename);
109 
110 		//fprintf(stdout,"Found filename `%s' (%s)\n", filename, pagename);
111 
112 		/* compare this file with the file we're looking for */
113 		if (strcmp(pagename,bname) == 0)
114 		{
115 			/* we found a match! */
116 			matched++;
117 			/* put it in the buffer */
118 			strncat(Buf, filename, 1023-strlen(Buf));
119 
120 			/* clean up, and exit the loop */
121 			xfree(filename);
122 			xfree(pagename);
123 			break;
124 		}
125 		xfree(filename);
126 		xfree(pagename);
127 	}
128 	closedir(dir);
129 
130 	if (matched) return 1;
131 
132 	return 0;
133 #undef Buf
134 }
135 
136 FILE *
dirpage_lookup(char ** type,char *** message,unsigned long * lines,char * filename,char ** first_node)137 dirpage_lookup(char **type, char ***message, unsigned long *lines,
138 		char *filename, char **first_node)
139 {
140 #define Type	(*type)
141 #define Message	(*message)
142 #define Lines	(*lines)
143 	FILE *id = 0;
144 	int filenamelen = strlen(filename);
145 	int goodHit = 0;
146 	char name[256];
147 	char file[256];
148 	char *nameend, *filestart, *fileend, *dot;
149 
150 	id = opendirfile(0);
151 	if (!id)
152 		return NULL;
153 
154 	read_item(id, type, message, lines);
155 
156 	/* search for node-links in every line */
157 	for (unsigned long i = 1; i < Lines; i++)
158 	{
159 		if ( (Message[i][0] == '*') && (Message[i][1] == ' ')
160 				&& ( nameend = strchr(Message[i], ':') )
161 				&& (*(nameend + 1) != ':')	/* form: `* name:(file)node.' */
162 				&& (filestart = strchr(nameend, '(') )
163 				&& (fileend = strchr(filestart, ')') )
164 				&& (dot = strchr(fileend, '.') )
165 				&& (strncasecmp(filename, Message[i] + 2, filenamelen) == 0)
166 		   )
167 		{
168 			char *tmp;
169 
170 			/* skip this hit if it is not a perfect match and
171 			 * we have already found a previous partial match */
172 			if ( ! ( (nameend - Message[i]) - 2 == filenamelen )
173 					&&	goodHit )
174 			{
175 				continue;
176 			}
177 
178 			/* find the name of the node link */
179 			tmp = name;
180 			strncpy(file, filestart + 1, fileend - filestart - 1);
181 			file[fileend - filestart - 1] = 0;
182 			strncpy(name, fileend + 1, dot - fileend - 1);
183 			name[dot - fileend - 1] = 0;
184 			while (isspace(*tmp)) tmp++;
185 
186 			if (strlen(name))
187 			{
188 				*first_node = xmalloc(strlen(tmp) + 1);
189 				strcpy((*first_node), tmp);
190 			}
191 
192 			/* close the previously opened file */
193 			if (id)
194 			{
195 				fclose(id);
196 				id = 0;
197 			}
198 
199 			/* see if this info file exists */
200 			id = openinfo(file, 0);
201 			if (id)
202 			{
203 				goodHit = 1;
204 			}
205 		}
206 	}
207 
208 	/* if we haven't found anything, clean up and exit */
209 	if (id && !goodHit)
210 	{
211 		fclose(id);
212 		id = 0;
213 	}
214 
215 	/* return file we found */
216 	return id;
217 #undef Lines
218 #undef Message
219 #undef Type
220 }
221 
222 void
freeitem(char ** type,char *** buf,unsigned long * lines)223 freeitem(char **type, char ***buf, unsigned long *lines)
224 {
225 #define Type	(*type)
226 #define Buf		(*buf)
227 #define Lines	(*lines)
228 
229 	if (Type != 0)
230 	{
231 		xfree(Type);
232 		Type = 0;
233 	}
234 	if (Buf != 0)
235 	{
236 		for (unsigned long i = 1; i <= Lines; i++)
237 			if (Buf[i] != 0)
238 			{
239 				xfree(Buf[i]);
240 				Buf[i] = 0;
241 			}
242 		xfree(Buf);
243 		Buf = 0;
244 	}
245 #undef Type
246 #undef Buf
247 #undef Lines
248 }
249 
250 void
read_item(FILE * id,char ** type,char *** buf,unsigned long * lines)251 read_item(FILE * id, char **type, char ***buf, unsigned long *lines)
252 {
253 
254 #define Type	(*type)
255 #define Buf		(*buf)
256 #define Lines	(*lines)
257 	int i;
258 
259 	freeitem(type, buf, lines);	/* free previously allocated memory */
260 
261 	/* set number of lines to 0 */
262 	Lines = 0;
263 
264 	/* initial buffer allocation */
265 	Buf = xmalloc(sizeof(char **));
266 
267 	/* seek precisely on the INFO_TAG (the seeknode function may be imprecise
268 	 * in combination with some weird tag_tables).  */
269 	while (!feof(id) && fgetc(id) != INFO_TAG);
270 	/* then skip the trailing `\n' */
271 	while (!feof(id) && fgetc(id) != '\n');
272 
273 	/* allocate and read the header line */
274 	Type = xmalloc(1024);
275 	if (fgets(Type, 1024, id)==NULL)
276 	{
277 		/* nothing to do */
278 		return;
279 	}
280 	Type = xrealloc(Type, strlen(Type) + 1);
281 	/* now iterate over the lines */
282 	do
283 	{
284 		/* don't read after eof in info file */
285 		if (feof(id))
286 			break;
287 
288 		/* realloc the previous line for it to fit exactly */
289 		if (Lines)
290 		{
291 			Buf[Lines] = xrealloc(Buf[Lines], strlen(Buf[Lines]) + 1);
292 		}
293 
294 		/* TODO: Weirdness going on here; looks like off-by-one error as Buf[0] is always "\0" */
295 		/* increase the read lines number */
296 		Lines++;
297 
298 		/* allocate space for the new line */
299 		Buf = xrealloc(Buf, sizeof(char **) *(Lines + 1));
300 		Buf[Lines] = xmalloc(1024);
301 		Buf[Lines][0] = 0;
302 
303 		/* if the line was not found in input file, fill the allocated space
304 		 * with empty line.  */
305 		if (fgets(Buf[Lines], 1024, id) == NULL)
306 			strcpy(Buf[Lines], "\n");
307 		else /* we can be sure that at least 1 char was read! */
308 		{
309 			/* *sigh*  indices contains \0's
310 			 * which totally fucks up all strlen()s.
311 			 * so replace it by a space */
312 			i = 1023;
313 			/* find the end of the string */
314 			while (Buf[Lines][i]=='\0' && i>=0) i--;
315 			/* and replace all \0's in the rest of the string by spaces */
316 			while (i>=0)
317 			{
318 				if (Buf[Lines][i]=='\0' || Buf[Lines][i]=='\b')
319 					Buf[Lines][i]=' ';
320 				i--;
321 			}
322 		}
323 	}
324 	while (Buf[Lines][0] != INFO_TAG);	/* repeat until new node mark is found */
325 
326 
327 	/* added for simplifing two-line ismenu and isnote functs */
328 	if (Lines)
329 	{
330 		strcpy(Buf[Lines], "\n");
331 		Buf[Lines] = xrealloc(Buf[Lines], strlen(Buf[Lines]) + 1);
332 	}
333 
334 	fseek(id, -2, SEEK_CUR);
335 
336 #undef Type
337 #undef Buf
338 #undef Lines
339 
340 }
341 void
load_indirect(char ** message,unsigned long lines)342 load_indirect(char **message, unsigned long lines)
343 {
344 	char *wsk;
345 	int cut = 0;			/* number of invalid entries */
346 	indirect = xmalloc((lines + 1) * sizeof(Indirect));
347 	for (unsigned long i = 1; i < lines; i++)
348 	{
349 		char *check;
350 		wsk = message[i];
351 		check = wsk + strlen(wsk);
352 		while (*(++wsk) != ':')	/* check if this line keeps a real entry */
353 		{
354 			if (wsk == check)	/*
355 								 * make sure wsk won't go out of range
356 								 * in case the wsk would be corrupted.
357 								 */
358 				break;
359 		}
360 		if (*wsk)			/* if the entry holds some data... */
361 		{
362 			(*wsk) = 0;
363 			strncpy(indirect[i - cut].filename, message[i], 200);
364 			(*wsk) = ':';
365 			indirect[i - cut].offset = atoi(wsk + 2);
366 		}
367 		else
368 			cut++;			/* if the entry was invalid, make inirect count shorter */
369 	}
370 	IndirectEntries = lines - 1 - cut;
371 }
372 
373 void
load_tag_table(char ** message,unsigned long lines)374 load_tag_table(char **message, unsigned long lines)
375 {
376 	char *wsk, *wsk1;
377 	int is_indirect = 0;
378 	register unsigned int j;
379 	register char *res;
380 	int cut = 0;			/* holds the number of corrupt lines */
381 
382 	/*
383 	 * if in the first line there is a(indirect) string, skip that line
384 	 * by adding the value of is_indirect=1 to all message[line] references.
385 	 */
386 	if (strcasecmp("(Indirect)", message[1]) == 0)
387 		is_indirect = 1;
388 	tag_table = xmalloc((lines + 1) * sizeof(TagTable));
389 	for (unsigned long i = 1; i < lines - is_indirect; i++)
390 	{
391 		char *check;
392 		wsk = message[i + is_indirect];
393 		check = wsk + strlen(wsk);
394 		while (!isspace(*(++wsk)))
395 		{
396 			if (wsk >= check)
397 			{
398 				wsk--;
399 				break;
400 			}
401 		}
402 		wsk++;
403 		wsk1 = wsk;
404 		check = wsk1 + strlen(wsk1);
405 		while (*(++wsk1) != INDIRECT_TAG)
406 		{
407 			if (wsk1 >= check)
408 				break;
409 		}
410 		if (wsk1 < check)
411 		{
412 			(*wsk1) = 0;
413 			/*
414 			 * original: sprintf(tag_table[i-cut].nodename,"%s",wsk);
415 			 * below is a faster version.
416 			 */
417 			res = memcpy(tag_table[i - cut].nodename, wsk, j =(size_t)(wsk1 - wsk));
418 			(*(res += j + 1)) = 0;
419 			(*wsk1) = INDIRECT_TAG;
420 			wsk1++;
421 			tag_table[i - cut].offset = atoi(wsk1);
422 		}
423 		else
424 			cut++;			/* increment the number of corrupt entries */
425 	}
426 	TagTableEntries = lines - 1 - is_indirect - cut;
427 
428 	/* FIXME: info should ALWAYS start at the 'Top' node, not at the first
429 	   mentioned node(vide ocaml.info) */
430 
431 	for (unsigned int i = 1; i <= TagTableEntries; i++)
432 	{
433 		if (strcasecmp(tag_table[i].nodename, "Top") == 0)
434 		{
435 			FirstNodeOffset = tag_table[i].offset;
436 			strcpy(FirstNodeName, tag_table[i].nodename);
437 		}
438 	}
439 	qsort(&tag_table[1], TagTableEntries, sizeof(TagTable), qsort_cmp);
440 }
441 
442 /* TODO: seek_indirect() and seek_tag_table() are almost identical: remove duplicate code */
443 int
seek_indirect(FILE * id)444 seek_indirect(FILE * id)
445 {
446 	int finito = 0;
447 	long seek_pos;
448 	int input;
449 	char *type = xmalloc(1024);
450 	fseek(id, 0, SEEK_SET);
451 	while (!finito)		/*
452 						 * scan through the file, searching for "indirect:"
453 						 * string in the type(header) line of node.
454 						 */
455 	{
456 		while ((input = fgetc(id)) != INFO_TAG)
457 			if (input == EOF)
458 			{
459 				if (type)
460 				{
461 					xfree(type);
462 					type = 0;
463 				}
464 				return 0;
465 			}
466 		seek_pos = ftell(id) - 2;
467 		fgetc(id);
468 		if (fgets(type, 1024, id)==0)
469 		{
470 			/* we're at the end of the file and haven't found any indirect refs. so bail out */
471 			if (type)
472 			{
473 				xfree(type);
474 				type = 0;
475 			}
476 			return 0;
477 		}
478 		if (strncasecmp("Indirect:", type, strlen("Indirect:")) == 0)
479 		{
480 			finito = 1;
481 		}
482 	}
483 	xfree(type);
484 	type = 0;
485 	if (!curses_open)
486 	{
487 		printf(_("Searching for indirect done"));
488 		printf("\n");
489 	}
490 	else
491 	{
492 		attrset(bottomline);
493 		mvhline(maxy - 1, 0, ' ', maxx);
494 		mvaddstr(maxy - 1, 0, _("Searching for indirect done"));
495 		attrset(normal);
496 	}
497 	fseek(id, seek_pos, SEEK_SET);
498 	return 1;
499 }
500 
501 /*
502  * second arg for dumping out verbose debug info or not :)
503  */
504 int
seek_tag_table(FILE * id,int quiet)505 seek_tag_table(FILE * id,int quiet)
506 {
507 	int finito = 0;
508 	long seek_pos;
509 	int input;
510 	char *type = xmalloc(1024);
511 	fseek(id, 0, SEEK_SET);
512 	/*
513 	 * Scan through the file, searching for a string
514 	 * "Tag Table:" in the type(header) line of node.
515 	 */
516 	while (!finito)
517 	{
518 		while ((input = fgetc(id)) != INFO_TAG)
519 		{
520 			if (input == EOF)
521 			{
522 				if (!quiet)
523 				{
524 					if (!curses_open)
525 					{
526 						printf(_("Warning: could not find tag table"));
527 						printf("\n");
528 					}
529 					else
530 					{
531 						attrset(bottomline);
532 						mvhline(maxy - 1, 0, ' ', maxx);
533 						mvaddstr(maxy - 1, 0, _("Warning: could not find tag table"));
534 						attrset(normal);
535 					}
536 				}
537 				if (type)
538 				{
539 					xfree(type);
540 					type = 0;
541 				}
542 				return 2;
543 			}
544 		}
545 		seek_pos = ftell(id) - 2;
546 		while (fgetc(id) != '\n');
547 		if (fgets(type, 1024, id)==NULL)
548 		{
549 			/* we're at the end of the file and haven't found a tag table. so bail out */
550 			if (type)
551 			{
552 				xfree(type);
553 				type = 0;
554 			}
555 			return 2;
556 		}
557 		if (strncasecmp("Tag Table:", type, strlen("Tag Table:")) == 0)
558 		{
559 			finito = 1;
560 		}
561 	}
562 	xfree(type);
563 	type = 0;
564 	if (!curses_open)
565 		printf(_("Searching for tag table done\n"));
566 	else
567 	{
568 		attrset(bottomline);
569 		mvhline(maxy - 1, 0, ' ', maxx);
570 		mvaddstr(maxy - 1, 0, "Searching for tag table done");
571 		attrset(normal);
572 	}
573 	fseek(id, seek_pos, SEEK_SET);
574 	return 1;
575 }
576 
577 void
buildcommand(char * dest,char * command,char * filename,const char * tmpfilename)578 buildcommand(char *dest, char *command, char *filename, const char *tmpfilename)
579 {
580 	strcpy(dest, command);
581 	strcat(dest, " ");
582 	strcat(dest, filename);
583 	strcat(dest, "> ");
584 	strcat(dest, tmpfilename);
585 }
586 
587 void
builddircommand(char * dest,char * command,char * filename,const char * tmpfilename)588 builddircommand(char *dest, char *command, char *filename, const char *tmpfilename)
589 {
590 	strcpy(dest, command);
591 	strcat(dest, " ");
592 	strcat(dest, filename);
593 	strcat(dest, ">> ");
594 	strcat(dest, tmpfilename);
595 }
596 
597 FILE *
opendirfile(int number)598 opendirfile(int number)
599 {
600 	FILE *id = NULL;
601 	char buf[1024];		/* holds local copy of filename */
602 	char *bufend;			/* points at the trailing 0 of initial name */
603 	char command[1128];		/* holds command to evaluate for decompression of file */
604 	int i, j;
605 	char *tmpfilename = NULL;
606 	size_t *fileendentries = xmalloc(infopathcount * sizeof(*fileendentries)); /* should really be off_t, but a signed type really doesn't make sense here */
607 	int dir_found = 0;
608 	int dircount = 0;
609 	int lang_found;
610 	struct stat status;
611 
612 	if (number == 0)		/* initialize tmp filename for file 1 */
613 	{
614 		/* close and delete old tmp file */
615 		if (tmpfilename1)
616 		{
617 			unlink(tmpfilename1);	/* erase old tmpfile */
618 			free(tmpfilename1);
619 		}
620 		tmpfilename1 = make_tempfile();
621 		tmpfilename = tmpfilename1;	/* later we will refere only to tmp1 */
622 	}
623 	for (i = 0; i < infopathcount; i++)	/* go through all paths */
624 	{
625 		lang_found = 0;
626 		strcpy(buf, infopaths[i]);	/* build a filename */
627 		strcat(buf, "/");
628 		if (getenv("LANG") != NULL)
629 			strcat(buf, getenv("LANG"));
630 		strcat(buf, "/dir");
631 		/*
632 		 * remember the bufend to make it
633 		 * possible later to glue compression suffixes.
634 		 */
635 		bufend = buf;
636 		bufend += strlen(buf);
637 		for (j = 0; j < SuffixesNumber; j++)	/* go through all suffixes */
638 		{
639 			strcat(buf, suffixes[j].suffix);
640 			if ((id = fopen(buf, "r")) != NULL)
641 			{
642 				fclose(id);
643 				builddircommand(command, suffixes[j].command, buf, tmpfilename);
644 				xsystem(command);
645 				lstat(tmpfilename, &status);
646 				fileendentries[dircount] = status.st_size;
647 				dircount++;
648 				dir_found = 1;
649 				lang_found = 1;
650 			}
651 			(*bufend) = 0;
652 		}
653 
654 		/* same as above, but without $LANG support */
655 		if (!lang_found)
656 		{
657 			strcpy(buf, infopaths[i]);	/* build a filename */
658 			strcat(buf, "/");
659 			strcat(buf, "dir");
660 			/*
661 			 * remember the bufend to make it possible later to glue
662 			 * compression suffixes.
663 			 */
664 			bufend = buf;
665 			bufend += strlen(buf);
666 			for (j = 0; j < SuffixesNumber; j++)	/* go through all suffixes */
667 			{
668 				strcat(buf, suffixes[j].suffix);
669 				if ((id = fopen(buf, "r")) != NULL)
670 				{
671 					fclose(id);
672 					builddircommand(command, suffixes[j].command, buf, tmpfilename);
673 					xsystem(command);
674 					lstat(tmpfilename, &status);
675 					fileendentries[dircount] = status.st_size;
676 					dircount++;
677 					dir_found = 1;
678 				}
679 				(*bufend) = 0;
680 			}
681 		}
682 	}
683 	if (dir_found)
684 		id = fopen(tmpfilename, "r");
685 	/*
686 	 * Filter the concatenated dir pages to exclude hidden parts of info
687 	 * entries
688 	 */
689 	if (id)
690 	{
691 		char *tmp;
692 		size_t filelen, l;
693 		int aswitch = 0;
694 		int firstswitch = 0;
695 		dircount = 0;
696 
697 		fseek(id, 0, SEEK_END);
698 		filelen = ftell(id);
699 
700 		tmp = xmalloc(filelen);
701 		fseek(id, 0, SEEK_SET);
702 		if (fread(tmp, 1, filelen, id)!=filelen)
703 		{
704 			printf(_("Error while reading file '%s'"), tmp);
705 			closeprogram();
706 			exit(1);
707 		}
708 		fclose(id);
709 		id = fopen(tmpfilename, "w");
710 		for (l = 0; l < filelen; l++)
711 		{
712 			if (tmp[l] == INFO_TAG)
713 			{
714 				aswitch ^= 1;
715 				if (!firstswitch)
716 					fputc(tmp[l], id);
717 				firstswitch = 1;
718 			}
719 			else if ((aswitch) ||(!firstswitch))
720 				fputc(tmp[l], id);
721 			if (l + 1 == fileendentries[dircount])
722 			{
723 				if (aswitch != 0)
724 					aswitch = 0;
725 				dircount++;	/* the last dircount should fit to the end of filelen */
726 			}
727 		}
728 		fputc(INFO_TAG, id);
729 		fputc('\n', id);
730 		xfree(fileendentries);
731 		fclose(id);
732 		id = fopen(tmpfilename, "r");
733 		xfree(tmp);
734 
735 		return id;
736 	}
737 	return NULL;
738 }
739 
740 /*
741  * Note: openinfo is a function for reading info files, and putting
742  * uncompressed content into a temporary filename.  For a flexibility, there
743  * are two temporary files supported, i.e.  one for keeping opened info file,
744  * and second for i.e. regexp search across info nodes, which are in other
745  * info-subfiles.  The temporary file 1 is refrenced by number=0, and file 2 by
746  * number=1 Openinfo by default first tries the path stored in char
747  * *filenameprefix and then in the rest of userdefined paths.
748  */
749 FILE *
openinfo(char * filename,int number)750 openinfo(char *filename, int number)
751 {
752 	FILE *id = NULL;
753 #define BUF_LEN 1024
754 	char *buf = xmalloc(BUF_LEN);	/* holds local copy of filename */
755 	char *bufend;			/* points at the trailing 0 of initial name */
756 	char command[1128];		/* holds command to evaluate for decompression of file */
757 	int i, j;
758 	char *tmpfilename;
759 
760 	if ((strncmp(filename, "dir", 3)==0)  &&  !isalnum(filename[3]))
761 	{
762 		xfree(buf);
763 		return opendirfile(number);
764 	}
765 
766 	if (number == 0)		/* initialize tmp filename for file 1 */
767 	{
768 		if (tmpfilename1)
769 		{
770 			unlink(tmpfilename1);	/* erase old tmpfile */
771 			free(tmpfilename1);
772 		}
773 		tmpfilename1 = make_tempfile();
774 		tmpfilename = tmpfilename1;	/* later we will refere only to tmp1 */
775 	}
776 	else /* initialize tmp filename for file 2 */
777 	{
778 		if (tmpfilename2)
779 		{
780 			unlink(tmpfilename2);	/* erase old tmpfile */
781 			free(tmpfilename2);
782 		}
783 		tmpfilename2 = make_tempfile();
784 		tmpfilename = tmpfilename2;	/* later we will refere only to tmp2 */
785 	}
786 
787 	for (i = -2; i < infopathcount; i++)	/* go through all paths */
788 	{
789 		if (i < 0)
790 		{
791 			/*
792 			 * no filenameprefix, we don't navigate around any specific
793 			 * infopage set, so simply scan all directories for a hit
794 			 */
795 			if (!filenameprefix)
796 				continue;
797 			/* build a filename: First (i == -2) try filenameprefix/filename,
798 			 * then try with a .info appended */
799 			if (i == -2)
800 				snprintf(buf, BUF_LEN, "%s/%s", filenameprefix, basename(filename));
801 			else
802 				snprintf(buf, BUF_LEN, "%s/%s.info", filenameprefix, basename(filename));
803 		}
804 		else
805 		{
806 			/* build a filename */
807 			strcpy(buf, infopaths[i]);
808 			/* no match found in this directory */
809 			if (! matchfile(&buf, filename))
810 				continue;
811 		}
812 		bufend = buf;
813 		/* remember the bufend to make it possible later to glue compression
814 		 * suffixes. */
815 		bufend += strlen(buf);
816 		for (j = 0; j < SuffixesNumber; j++)	/* go through all suffixes */
817 		{
818 			strcat(buf, suffixes[j].suffix);
819 			if ((id = fopen(buf, "r")) != NULL)
820 			{
821 				fclose(id);
822 				clearfilenameprefix();
823 				filenameprefix = strdup(buf);
824 				{			/* small scope for removal of filename */
825 					int prefixi, prefixlen = strlen(filenameprefix);
826 					for (prefixi = prefixlen; prefixi > 0; prefixi--)
827 						if (filenameprefix[prefixi] == '/')
828 						{
829 							filenameprefix[prefixi] = 0;
830 							break;
831 						}
832 				}
833 				buildcommand(command, suffixes[j].command, buf, tmpfilename);
834 				xsystem(command);
835 				id = fopen(tmpfilename, "r");
836 				if (id)
837 				{
838 					xfree(buf);
839 					return id;
840 				}
841 			}
842 			(*bufend) = 0;
843 		}
844 
845 		/* if we have a nonzero filename prefix, that is we view a set of
846 		 * infopages, we don't want to search for a page in all
847 		 * directories, but only in the prefix directory.  Therefore break
848 		 * here. */
849 		if ((i == -1) &&(filenameprefix))
850 			break;
851 	}
852 	xfree(buf);
853 
854 
855 	return 0;
856 #undef BUF_LEN
857 }
858 
859 	void
addrawpath(char * filename)860 addrawpath(char *filename)
861 {
862 	int len = strlen(filename);
863 	int i, pos;
864 	char tmp = '\0';
865 	for (i = len; i >= 0; i--)
866 	{
867 		if (filename[i] == '/')
868 		{
869 			tmp = filename[i+1];
870 			filename[i+1] = 0;
871 			pos = i+1;
872 			break;
873 		}
874 	}
875 	if (i < 0)
876 		pos = -1;
877 
878 	infopaths = xrealloc(infopaths,(infopathcount + 3) *(sizeof(char *)));
879 	for (i = infopathcount; i > 0; i--)	/* move entries to the right */
880 		infopaths[i] = infopaths[i - 1];
881 
882 	if (pos > 0)
883 		infopaths[0]=strdup(filename);	/* add new(raw) entry */
884 	else
885 		infopaths[0]=strdup("./");
886 	infopathcount++;
887 
888 	if (pos > 0)			/* recreate original filename */
889 		filename[pos] = tmp;
890 }
891 
892 	int
isininfopath(char * name)893 isininfopath(char *name)
894 {
895 	int i;
896 	for (i = 0; i < infopathcount; i++)
897 	{
898 		if (strcmp(name, infopaths[i]) == 0)
899 			return 1;		/* path already exists */
900 	}
901 	return 0;			/* path not found in previous links */
902 }
903 
904 /* returns the number of chars ch in string str */
905 unsigned int
charcount(const char * str,const char ch)906 charcount(const char *str, const char ch)
907 {
908 	int num = 0;
909 	const char *c;
910 
911 	c = str;
912 
913 	while (*c != '\0')
914 	{
915 		if (*c++ == ch)
916 			num++;
917 	}
918 	return num;
919 }
920 
921 /*
922  * find the paths where info files are to be found,
923  * and put them in the global var infopaths[]
924  */
925 void
initpaths()926 initpaths()
927 {
928 	char emptystr[1] = "";
929 	char **paths = NULL;
930 	char *infopath = NULL, *langpath = NULL;
931 	char *c, *dir, *env, *next;
932 	char *rawlang = NULL, *lang = NULL, *langshort = NULL;
933 	int ret;
934 	unsigned int i, j, maxpaths, numpaths = 0, infolen, langlen;
935 	size_t len;
936 	struct stat sbuf;
937 	ino_t *inodes;
938 
939 	/* first concat the paths */
940 	env = getenv("INFOPATH");
941 	if (env == NULL)
942 	{
943 		env = emptystr;
944 	}
945 	infolen = strlen(env) + strlen(configuredinfopath) + 3;
946 	infopath = (char *) xmalloc( infolen );
947 	strcat(infopath, env);
948 	strcat(infopath, ":");
949 	strcat(infopath, configuredinfopath);
950 	/* end with a :, otherwise the strchr below will fail for the last entry */
951 	strcat(infopath, ":");
952 
953 	/* alloc the paths[] array */
954 	maxpaths = 3 * (charcount( infopath, ':' ) + 1); // *3 for $LANG
955 	paths = (char **) xmalloc( maxpaths * sizeof(char *) );
956 
957 	/* split at ':' and put the path components into paths[] */
958 	dir = infopath;
959 	/* if this actually is a non-empty string, add it to paths[] */
960 	while ( (next = strchr(dir, ':')) != NULL )
961 	{
962 		*next = '\0';  /* terminate the string */
963 
964 		/* if the dir actually is a non-empty string, add it to paths[] */
965 		if ( dir && strlen(dir)>0 )
966 		{
967 			paths[numpaths++] = dir;
968 		}
969 
970 		/* and advance the pointer to the next entry */
971 		dir = next+1;
972 	}
973 
974 	/* get the current $LANG, if any (to use for localized info pages) */
975 	rawlang = getenv("LANG");
976 	if (rawlang) {
977 		lang = strdup(rawlang);
978 		/* fix the lang string */
979 		for (i=0; lang[i]!='\0'; i++)
980 		{
981 			/* cut off the charset */
982 			if (lang[i]=='.')
983 			{
984 				lang[i]='\0';
985 			}
986 			/* if lang is sublocalized (nl_BE or so), also use short version */
987 			if (lang[i]=='_' && langshort==NULL)
988 			{
989 				langshort = strdup(lang);
990 				langshort[i] = '\0';
991 			}
992 		}
993 	}
994 	/* if we have a LANG defined, add paths with this lang to the paths[] */
995 	if (lang && strlen(lang)>0 )
996 	{
997 		/* crude upper limit */
998 		langlen = infolen + (strlen(lang)+2) * numpaths + 1;
999 		if (langshort!=NULL) langlen *= 2;
1000 		langpath = (char *) xmalloc( langlen * sizeof(char) );
1001 
1002 		c = langpath;
1003 		for (i=0; i<numpaths; i++)
1004 		{
1005 			/* TODO: check for negative return values of sprintf */
1006 			len = sprintf(c, "%s/%s", paths[i], lang);
1007 			/* add the lang specific dir at the beginning */
1008 			paths[numpaths+i] = paths[i];
1009 			paths[i] = c;
1010 
1011 			c += len+1;
1012 
1013 			if (langshort)
1014 			{
1015 				/* TODO: check for negative return values of sprintf */
1016 				len = sprintf(c, "%s/%s", paths[numpaths+i], langshort);
1017 				/* add the lang specific dir at the beginning */
1018 				paths[2*numpaths+i] = paths[numpaths+i];
1019 				paths[numpaths+i] = c;
1020 
1021 				c += len+1;
1022 			}
1023 
1024 		}
1025 		numpaths *= (langshort?3:2);
1026 	}
1027 
1028 #ifdef ___DEBUG___
1029 	/* for debugging */
1030 	for (i=0; i<numpaths; i++)
1031 		fprintf(stderr,"--> %s\n", paths[i]);
1032 #endif
1033 
1034 	/* ok, now we have all the (possibly) revelevant paths in paths[] */
1035 	/* now loop over them, see if they are valid and if they are duplicates*/
1036 	/* TODO: cleanup all malloc calls (get rid of cast, use sizeof(varname) instead of sizeof(type) */
1037 	inodes = (ino_t *) xmalloc( maxpaths * sizeof(ino_t) );
1038 	numpaths = 0;
1039 	len = 0;
1040 	for (i=0; i< maxpaths; i++)
1041 	{
1042 		/* TODO: check where these NULL paths come from */
1043 		if (paths[i]==NULL)
1044 			continue;
1045 
1046 		/* stat() the dir */
1047 		ret = stat( paths[i], &sbuf);
1048 		/* and see if it could be opened */
1049 		if (ret < 0)
1050 		{
1051 #ifdef ___DEBUG___
1052 			fprintf(stderr, "error while opening `%s': %s\n",
1053 					paths[i], strerror(errno) );
1054 #endif
1055 			paths[i] = NULL;
1056 			inodes[i] = 0;
1057 		}
1058 		else
1059 		{
1060 			inodes[i] = sbuf.st_ino;
1061 		}
1062 
1063 		/* now check if this path is a duplicate */
1064 		for (j=0; j<i; j++)
1065 		{
1066 			if (inodes[j]==inodes[i]) paths[i] = NULL;
1067 		}
1068 
1069 		/* calculate the total number of vali paths and the size of teh strings */
1070 		if (paths[i]!=NULL)
1071 		{
1072 			numpaths++;
1073 			len += strlen(paths[i]) + 1;
1074 		}
1075 	}
1076 
1077 
1078 	/* and alloc and copy to global var */
1079 	infopathcount = numpaths;
1080 	infopaths = (char **) xmalloc( numpaths * sizeof(char *) );
1081 	c = (char *) xmalloc( len * sizeof(char) );
1082 	j=0;
1083 	for (i=0; i<maxpaths; i++)
1084 	{
1085 		if (paths[i]!=NULL)
1086 		{
1087 			/* copy path to c buffer */
1088 			strcpy(c, paths[i]);
1089 			infopaths[j++] = c;
1090 			c += strlen(paths[i]) + 1;
1091 		}
1092 	}
1093 
1094 
1095 	xfree(infopath);
1096 	xfree(langpath);
1097 	xfree(paths);
1098 	xfree(lang);
1099 	xfree(langshort);
1100 	xfree(inodes);
1101 
1102 #ifdef ___DEBUG___
1103 	/* for debugging */
1104 	fprintf(stderr, "%i valid info paths found:\n", infopathcount);
1105 	for (i=0; i<infopathcount; i++)
1106 		if (infopaths[i]) fprintf(stderr,"--> %s\n", infopaths[i]);
1107 #endif
1108 
1109 
1110 }
1111 
1112 
1113 
1114 void
create_indirect_tag_table()1115 create_indirect_tag_table()
1116 {
1117 	FILE *id = 0;
1118 	int initial;
1119 	for (unsigned i = 1; i <= IndirectEntries; i++)
1120 	{
1121 		id = openinfo(indirect[i].filename, 1);
1122 		initial = TagTableEntries + 1;
1123 		if (!id)
1124 		{
1125 			/* display error message to make the user aware of
1126 			 * the broken info page
1127 			 */
1128 			char msg[1024];
1129 			snprintf(msg, 1024, "%s '%s' (%s)",
1130 				_("Can't open file"), indirect[i].filename,
1131 				_("press a key to continue") );
1132 			attrset(bottomline);
1133 			mvhline(maxy - 1, 0, ' ', maxx);
1134 			mvaddstr(maxy - 1, 0, msg);
1135 			move(0, 0);
1136 			attrset(normal);
1137 			getch();
1138 
1139 			continue;
1140 		}
1141 		create_tag_table(id);
1142 		FirstNodeOffset = tag_table[1].offset;
1143 		strcpy(FirstNodeName, tag_table[1].nodename);
1144 		fclose(id);
1145 		for (unsigned j = initial; j <= TagTableEntries; j++)
1146 		{
1147 			tag_table[j].offset +=(indirect[i].offset - FirstNodeOffset);
1148 		}
1149 	}
1150 	FirstNodeOffset = tag_table[1].offset;
1151 	strcpy(FirstNodeName, tag_table[1].nodename);
1152 	qsort(&tag_table[1], TagTableEntries, sizeof(TagTable), qsort_cmp);
1153 }
1154 void
create_tag_table(FILE * id)1155 create_tag_table(FILE * id)
1156 {
1157 	char *buf = xmalloc(1024);
1158 	long oldpos;
1159 	fseek(id, 0, SEEK_SET);
1160 	if (!tag_table)
1161 		tag_table = xmalloc((TagTableEntries + 2) * sizeof(TagTable));
1162 	else
1163 		tag_table = xrealloc(tag_table,(TagTableEntries + 2) * sizeof(TagTable));
1164 	while (!feof(id))
1165 	{
1166 		if (fgetc(id) == INFO_TAG)	/* We've found a node entry! */
1167 		{
1168 			while (fgetc(id) != '\n');	/* skip '\n' */
1169 			TagTableEntries++;	/* increase the nuber of tag table entries */
1170 			oldpos = ftell(id);	/* remember this file position! */
1171 			/*
1172 			 * it is a an eof-fake-node (in some info files it happens, that
1173 			 * the eof'ish end of node is additionaly signalised by an INFO_TAG
1174 			 * We give to such node an unlike to meet nodename.
1175 			 */
1176 			if (fgets(buf, 1024, id) == NULL)
1177 			{
1178 				tag_table = xrealloc(tag_table, sizeof(TagTable) *(TagTableEntries + 1));
1179 				strcpy(tag_table[TagTableEntries].nodename, "12#!@#4");
1180 				tag_table[TagTableEntries].offset = 0;
1181 			}
1182 			else
1183 			{
1184 				int colons = 0, i, j;
1185 				int buflen = strlen(buf);
1186 				for (i = 0; i < buflen; i++)
1187 				{
1188 					if (buf[i] == ':')
1189 						colons++;
1190 					if (colons == 2)	/*
1191 										 * the string after the second colon
1192 										 * holds the name of current node.
1193 										 * The name may then end with `.',
1194 										 * or with a newline, which is scanned
1195 										 * bellow.
1196 										 */
1197 					{
1198 						for (j = i + 2; j < buflen; j++)
1199 						{
1200 							if ((buf[j] == ',') ||(buf[j] == '\n'))
1201 							{
1202 								tag_table = xrealloc(tag_table, sizeof(TagTable) *(TagTableEntries + 1));
1203 								buf[j] = 0;
1204 								buflen = j;
1205 								strcpy(tag_table[TagTableEntries].nodename, buf + i + 2);
1206 								tag_table[TagTableEntries].offset = oldpos - 2;
1207 								break;
1208 							}
1209 						}
1210 						break;
1211 					}
1212 				}		/* end: for loop, looking for second colon */
1213 			}			/* end: not a fake node */
1214 		}			/* end: we've found a node entry(INFO_TAG) */
1215 	}				/* end: global while loop, looping until eof */
1216 	xfree(buf);
1217 	buf = 0;
1218 	if (!indirect)
1219 	{
1220 		FirstNodeOffset = tag_table[1].offset;
1221 		strcpy(FirstNodeName, tag_table[1].nodename);
1222 		qsort(&tag_table[1], TagTableEntries, sizeof(TagTable), qsort_cmp);
1223 	}
1224 }
1225 
1226 int
seeknode(int tag_table_pos,FILE ** Id)1227 seeknode(int tag_table_pos, FILE ** Id)
1228 {
1229 	int i;
1230 	FILE * newid;
1231 #define id	(*Id)
1232 	/*
1233 	 * Indirect nodes are seeked using a formula:
1234 	 * file-offset = tagtable_offset - indirect_offset +
1235 	 *             + tagtable[1]_offset
1236 	 */
1237 	if (indirect)
1238 	{
1239 		for (i = IndirectEntries; i >= 1; i--)
1240 		{
1241 			if (indirect[i].offset <= tag_table[tag_table_pos].offset)
1242 			{
1243 				long off = tag_table[tag_table_pos].offset - indirect[i].offset + FirstNodeOffset - 4;
1244 				newid = openinfo(indirect[i].filename, 0);
1245 				if (newid == NULL)
1246 				{
1247 					return -1;
1248 					closeprogram();
1249 					printf(_("Error: could not open info file part"));
1250 					printf("\n");
1251 					exit(1);
1252 				}
1253 				fclose(id);
1254 				id = newid;
1255 				if (off > 0)
1256 					fseek(id, off, SEEK_SET);
1257 				else
1258 					fseek(id, off, SEEK_SET);
1259 				break;
1260 			}
1261 		}
1262 	}
1263 	else
1264 	{
1265 		long off = tag_table[tag_table_pos].offset - 4;
1266 		if (off > 0)
1267 			fseek(id, off, SEEK_SET);
1268 		else
1269 			fseek(id, off, SEEK_SET);
1270 	}
1271 #undef id
1272 	return 0;
1273 }
1274 
1275 /* removes trailing .gz, .bz2, etc. */
1276 void
strip_compression_suffix(char * file)1277 strip_compression_suffix(char *file)
1278 {
1279 	const size_t len = strlen(file);
1280 	assert(len<1024); /* just some random limit */
1281 	char *found = 0;
1282 
1283 	for (unsigned j = 0; j < SuffixesNumber; j++)
1284 	{
1285 		if ( (found = strstr(file, suffixes[j].suffix)) != NULL )
1286 		{
1287 			if ( file + len == found + strlen(suffixes[j].suffix) )
1288 			{
1289 				*found = '\0';
1290 				break;
1291 			}
1292 		}
1293 	}
1294 }
1295 
1296 /* strip .info from and of string */
1297 void
strip_info_suffix(char * file)1298 strip_info_suffix(char *file)
1299 {
1300 	const size_t len = strlen(file);
1301 	assert(len<1024); /* just some random limit */
1302 
1303 	char *found = 0;
1304 	const char suffix[6] = ".info";
1305 
1306 	if ( (found = strstr(file, suffix)) != NULL )
1307 	{
1308 		if ( file + len == found + strlen(suffix) )
1309 		{
1310 			*found = '\0';
1311 		}
1312 	}
1313 }
1314 
1315