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  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of version 2 of the GNU General Public License as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful, but
12  *  WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
19  *  USA
20  ***************************************************************************/
21 
22 #include "common_includes.h"
23 
24 #include <regex.h>
25 #include <ctype.h>
26 #include <sys/select.h>
27 #include <sys/wait.h>
28 
29 #ifdef USE_WCHAR
30   #include <wchar.h>
31 #endif
32 
33 char *safe_user = "nobody";
34 char *safe_group = "nogroup";
35 
36 #ifndef HAVE_DECL_CURS_SET
37 void
curs_set(int a)38 curs_set(int a)
39 {
40 }
41 #endif
42 
43 #ifdef ___DONT_USE_REGEXP_SEARCH___
44 char *pinfo_re_pattern = 0;
45 #else
46 int pinfo_re_offset = -1;
47 #endif
48 
49 #ifdef HAS_READLINE
50 #include <readline/readline.h>
51 #include <readline/history.h>
52 /* HAS_READLINE */
53 #endif
54 
55 
56 /*
57  * the bellow define enables malloc/realloc/free logging to stderr.
58  * They start to log their argument values.
59  *
60  * #define ___DEBUG___
61  *
62  */
63 
64 #ifdef ___DEBUG___
65 unsigned long malloc_addr[1000];
66 unsigned long msizes[1000];
67 long addrescount = 0;
68 /* ___DEBUG___ */
69 #endif
70 
71 
72 int curses_open = 0;
73 
74 int shell_cursor = 1;
75 
76 void
xfree(void * ptr)77 xfree(void *ptr)
78 {
79 #ifdef ___DEBUG___
80 	int i, j;
81 	int flag = 0;
82 	unsigned long msize = 0;
83 	for (i = 0; i < addrescount; i++)
84 		msize += msizes[i];
85 	fprintf(stderr, "Size: %lu, count: %ld, freeing %lu\n", msize, addrescount,(unsigned long) ptr);
86 	for (i = 0; i < addrescount; i++)
87 		if (malloc_addr[i] ==(unsigned long) ptr)
88 		{
89 			flag = 1;
90 			for (j = i + 1; j < addrescount; j++)
91 			{
92 				malloc_addr[j - 1] = malloc_addr[j];
93 				msizes[j - 1] = msizes[j];
94 			}
95 			addrescount--;
96 			break;
97 		}
98 	if (flag == 0)
99 	{
100 		fprintf(stderr, "ERROR!!!\n");
101 		getchar();
102 	}
103 /* ___DEBUG___ */
104 #endif
105 	free(ptr);
106 }
107 
108 /* TODO: get rid of this xmalloc nonsense */
109 void *
xmalloc(size_t size)110 xmalloc(size_t size)
111 {
112 	register void *value = malloc(size);
113 #ifdef ___DEBUG___
114 	unsigned long msize = 0;
115 	int i;
116 /* ___DEBUG___ */
117 #endif
118 	if (value == 0)
119 	{
120 		closeprogram();
121 		printf(_("Virtual memory exhausted\n"));
122 		exit(1);
123 	}
124 #ifdef ___DEBUG___
125 	for (i = 0; i < addrescount; i++)
126 		msize += msizes[i];
127 	fprintf(stderr, "Size %lu, count: %ld, allocated %lu\n", msize, addrescount,(unsigned long) value);
128 	malloc_addr[addrescount] =(unsigned long) value;
129 	msizes[addrescount] = size;
130 	if (addrescount < 1000)
131 		addrescount++;
132 	else
133 	{
134 		fprintf(stderr, "trace buffer exhausted\n");
135 	}
136 /* ___DEBUG___ */
137 #endif
138 	memset(value, 0, size);
139 	return value;
140 }
141 
142 void *
xrealloc(void * ptr,size_t size)143 xrealloc(void *ptr, size_t size)
144 {
145 #ifdef ___DEBUG___
146 	int i, j, flag = 0;
147 	register void *value;
148 	unsigned long msize = 0;
149 	for (i = 0; i < addrescount; i++)
150 		msize += msizes[i];
151 	fprintf(stderr, "Size: %lu, count: %ld, reallocating %lu to ", msize, addrescount,(unsigned long) ptr);
152 	for (i = 0; i < addrescount; i++)
153 		if (malloc_addr[i] ==(unsigned long) ptr)
154 		{
155 			flag = 1;
156 			for (j = i + 1; j < addrescount; j++)
157 			{
158 				malloc_addr[j - 1] = malloc_addr[j];
159 				msizes[j - 1] = msizes[j];
160 			}
161 			addrescount--;
162 			break;
163 		}
164 	if (flag == 0)
165 	{
166 		fprintf(stderr, "ERROR!!!\n");
167 		getchar();
168 	}
169 	value = realloc(ptr, size);
170 #else
171 	register void *value = realloc(ptr, size + 1024);
172 /* ___DEBUG___ */
173 #endif
174 	if (value == 0)
175 	{
176 		closeprogram();
177 		printf(_("Virtual memory exhausted\n"));
178 		exit(1);
179 	}
180 #ifdef ___DEBUG___
181 	fprintf(stderr, "%lu, with size %lu\n",(unsigned long) value,(unsigned long) size);
182 	malloc_addr[addrescount] =(unsigned long) value;
183 	msizes[addrescount] = size;
184 	if (addrescount < 1000)
185 		addrescount++;
186 	else
187 	{
188 		fprintf(stderr, "trace buffer exhausted\n");
189 	}
190 /* ___DEBUG___ */
191 #endif
192 	return value;
193 }
194 
195 int
system_check(const char * command)196 system_check(const char *command)
197 {
198 	if (command==NULL)
199 	{
200 		return -1;
201 	}
202 	int result = system(command);
203 	if (WIFEXITED(result))
204 	{
205 		return WEXITSTATUS(result);
206 	}
207 	return -1;
208 }
209 
210 void
xsystem(const char * command)211 xsystem(const char *command)
212 {
213 	int result = system_check(command);
214 	if (result!=0)
215 	{
216 		printf(_("Failed to execute command '%s': %i"), command, result);
217 		exit(2);
218 	}
219 }
220 
221 void
initlocale()222 initlocale()
223 {
224 #ifdef ___DEBUG___
225 	int i;
226 	for (i = 0; i < 1000; i++)
227 		malloc_addr[i] = 0;
228 /* ___DEBUG___ */
229 #endif
230 	setlocale(LC_ALL, "");
231 	bindtextdomain(PACKAGE, LOCALEDIR);
232 	textdomain(PACKAGE);
233 }
234 
235 void
mymvhline(int y,int x,char ch,int len)236 mymvhline(int y, int x, char ch, int len)
237 {
238 	int i;
239 	for (i = 0; i < len; i++)
240 		mvaddch(y, x + i, ch);
241 }
242 
243 void
checkfilename(char * filename)244 checkfilename(char *filename)
245 {
246 	if ((strchr(filename, '<')) ||
247 			(strchr(filename, '>')) ||
248 			(strchr(filename, '|')) ||
249 			(strchr(filename, '(')) ||
250 			(strchr(filename, ')')) ||
251 			(strchr(filename, '!')) ||
252 			(strchr(filename, '`')) ||
253 			(strchr(filename, '&')) ||
254 			(strchr(filename, ';')))
255 	{
256 		printf(_("Illegal characters in filename!\n*** %s\n"), filename);
257 		exit(1);
258 	}
259 }
260 
261 #ifdef HAS_READLINE
262 /* custom function that readline will use to display text */
263 void
my_rl_display()264 my_rl_display()
265 {
266 	static size_t len = 0;
267 
268 	/* if the user's input has changed, clear the entire line to remove possible leftover completions */
269 	size_t newlen = strlen(rl_line_buffer);
270 	if (newlen!=len)
271 	{
272 		mymvhline(maxy - 1, 0, ' ', maxx);
273 		len = newlen;
274 	}
275 
276 	/* go to the bottom line, print the prompt and buffer */
277 	attrset(bottomline);
278 	move(maxy-1,0);
279 
280 	printw("%s%s", rl_prompt, rl_line_buffer);
281 	refresh();
282 }
283 
284 void
my_rl_completion_display(char ** matches,int num_matches,int UNUSED (max_length))285 my_rl_completion_display(char **matches, int num_matches, int UNUSED(max_length))
286 {
287 	if (num_matches<1)
288 	{
289 		return;
290 	}
291 	/* redraw entire prompt line, appended with possible matches */
292 	move(maxy-1,0);
293 	printw("%s", rl_prompt);
294 	/* note: first entry is the entered text, matches start at index 1 */
295 	printw("%s  ", matches[0]);
296 	for (int i=1; i < num_matches+1; i++)
297 	{
298 		printw("%s ", matches[i]);
299 	}
300 	/* and return prompt to correct position */
301 	move(maxy-1, strlen(rl_prompt) + strlen(matches[0]) );
302 
303 	refresh();
304 }
305 
306 
307 /* note: if set, last string MUST be set to NULL */
308 static const char * const *completion_values = NULL;
309 
310 /* readline completion functions, see https://thoughtbot.com/blog/tab-completion-in-gnu-readline */
311 
312 /* this function is called for each attempted match */
313 char *
getstring_completion_generator(const char * text,int state)314 getstring_completion_generator(const char *text, int state)
315 {
316 	static int list_index, len;
317 	const char *name;
318 
319 	if (!state) {
320 		list_index = 0;
321 		len = strlen(text);
322 	}
323 
324 	while ((name = completion_values[list_index++]) && name!=NULL) {
325 		if (strncmp(name, text, len) == 0) {
326 			return strdup(name);
327 		}
328 	}
329 
330 	return NULL;
331 }
332 
333 /* this function is called when readline attempts completions.  Return a matching function or NULL for no matching */
334 char **
getstring_completion(const char * text,int UNUSED (start),int UNUSED (end))335 getstring_completion(const char *text, int UNUSED(start), int UNUSED(end))
336 {
337 	/* do not fall back to default filename completion */
338 	rl_attempted_completion_over = 1;
339 	rl_completion_append_character = '\0';
340 
341 	if (completion_values==NULL)
342 	{
343 		return NULL;
344 	}
345 	return rl_completion_matches(text, getstring_completion_generator);
346 }
347 
348 #endif
349 
350 const
completions_from_tag_table(TagTable * table,size_t num)351 char ** completions_from_tag_table(TagTable * table, size_t num)
352 {
353 	/* allocate an extra entry at the end, which is set to NULL to terminate the table */
354 	const char ** completions = calloc(num+1, sizeof(*completions));
355 	for (size_t i=0, j=0; i<num; i++)
356 	{
357 		if (isalnum(table[i].nodename[0]))
358 		{
359 			completions[j++] = table[i].nodename;
360 		}
361 	}
362 	return completions;
363 }
364 
365 
366 char *
getstring(char * prompt)367 getstring(char *prompt)
368 {
369 	return getstring_with_completion(prompt, NULL);
370 }
371 
372 char *
getstring_with_completion(char * prompt,const char * const * completions)373 getstring_with_completion(char *prompt, const char * const * completions)
374 {
375 	char *buf;
376 
377 #ifdef HAS_READLINE
378 	completion_values = completions;
379 	rl_attempted_completion_function = getstring_completion;
380 	rl_completion_display_matches_hook = my_rl_completion_display;
381 
382 	curs_set(1);
383 	mymvhline(maxy - 1, 0, ' ', maxx);
384 	move(maxy - 1, 0);
385 	refresh();
386 
387 	rl_readline_name = PACKAGE;
388 
389 	/* set display function for readline to my_rl_display and call readline */
390 	rl_redisplay_function = my_rl_display;
391 	buf = readline(prompt);
392 	if (buf && *buf)
393 		add_history(buf);
394 
395 	curs_set(0);
396 
397 #else
398 
399 	move(maxy - 1, 0);
400 	buf = readlinewrapper(prompt);
401 
402 #endif
403 
404 	return buf;
405 }
406 
407 void
init_curses()408 init_curses()
409 {
410 	FILE *f = fopen("/dev/tty", "r+");
411 	SCREEN *screen = newterm(NULL, f, f);
412 	set_term(screen);
413 	noecho();
414 	cbreak();
415 	keypad(stdscr, TRUE);
416 	/*  meta(stdscr, TRUE); */
417 	initcolors();
418 	shell_cursor = curs_set(0);
419 #ifdef CURSES_MOUSE
420 	if (grab_mouse)
421 	{
422 		mousemask(BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED, NULL);
423 	}
424 #endif
425 	curses_open = 1;
426 }
427 
428 
429 void
closeprogram()430 closeprogram()
431 {
432 	if (curses_open)
433 		myendwin();
434 	if (ClearScreenAtExit)
435 		xsystem("clear");
436 	else
437 		printf("\n");
438 	if (tmpfilename1)
439 	{
440 		unlink(tmpfilename1);
441 		xfree(tmpfilename1);
442 	}
443 	if (tmpfilename2)
444 	{
445 		unlink(tmpfilename2);
446 		xfree(tmpfilename2);
447 	}
448 }
449 
450 int
gettagtablepos_search_internal(char * node,int left,int right)451 gettagtablepos_search_internal(char *node, int left, int right)
452 {
453 	/* left+(right-left)/2 */
454 	int thispos = left +((right - left) >> 1);
455 	int compare_result = compare_tag_table_string(tag_table[thispos].nodename, node);
456 	if (compare_result == 0)
457 		return thispos;
458 	else
459 	{
460 		if (left == right)
461 			return -1;
462 		if (compare_result > 0)
463 		{
464 			if (thispos > left)
465 				return gettagtablepos_search_internal(node, left, thispos - 1);
466 			else
467 				return -1;
468 		}
469 		else if (compare_result < 0)
470 		{
471 			if (thispos < right)
472 				return gettagtablepos_search_internal(node, thispos + 1, right);
473 			else
474 				return -1;
475 		}
476 	}
477 	return -1;
478 }
479 
480 int
gettagtablepos(char * node)481 gettagtablepos(char *node)
482 {
483 	/* strip spaces from the beginning */
484 	while (1)
485 	{
486 		if ((*node != ' ') &&(*node != '\t'))
487 			break;
488 		node++;
489 	}
490 	return gettagtablepos_search_internal(node, 1, TagTableEntries);
491 }
492 
493 int
pinfo_getch()494 pinfo_getch()
495 {
496 	int key = getch();
497 	/* following key will be alt's value */
498 	if (key == META_KEY)
499 	{
500 		key = getch();
501 		key |= 0x200;
502 	}
503 	return key;
504 }
505 
506 void
waitforgetch()507 waitforgetch()
508 {
509 	int ret;
510 
511 	fd_set rdfs;
512 	FD_ZERO(&rdfs);
513 	FD_SET(0, &rdfs);
514 
515 	/* we might get interrupted by e.g. SIGTSTP/SIGCONT */
516 	do ret = select(1, &rdfs, NULL, NULL, NULL);
517 	while (ret == -1 && errno == EINTR);
518 }
519 
520 /* returns 0 on success, 1 on error */
521 int
pinfo_re_comp(char * name)522 pinfo_re_comp(char *name)
523 {
524 #ifdef ___DONT_USE_REGEXP_SEARCH___
525 	if (pinfo_re_pattern)
526 	{
527 		free(pinfo_re_pattern);
528 		pinfo_re_pattern = 0;
529 	}
530 	pinfo_re_pattern = strdup(name);
531 	return 0;
532 #else
533 	/* first see if we can compile the regexp */
534 	regex_t preg;
535 	if (regcomp(&preg, name, REG_ICASE) != 0)
536 	{
537 		/* compilation failed, so return */
538 		return -1;
539 	}
540 
541 	/* compilation succeeded */
542 	/* first make some space in h_regexp[] to store the compiled regexp */
543 	if (pinfo_re_offset == -1)
544 	{
545 		pinfo_re_offset = h_regexp_num;
546 		if (!h_regexp_num)
547 			h_regexp = malloc(sizeof(regex_t));
548 		else
549 			h_regexp = realloc(h_regexp, sizeof(regex_t) *(h_regexp_num + 1));
550 	}
551 	else
552 	{
553 		regfree(&h_regexp[pinfo_re_offset]);
554 	}
555 
556 	/* then copy the compiled expression into the newly allocated space */
557 	memcpy(&h_regexp[pinfo_re_offset], &preg, sizeof(preg));
558 
559 	/* and finally return 0 for success */
560 	return 0;
561 #endif
562 }
563 
564 int
pinfo_re_exec(char * name)565 pinfo_re_exec(char *name)
566 {
567 #ifdef ___DONT_USE_REGEXP_SEARCH___
568 	char *found;
569 	if (pinfo_re_pattern)
570 	{
571 		found = strstr(name, pinfo_re_pattern);
572 		if (found != NULL)
573 			return 1;
574 		else
575 			return 0;
576 	}
577 #else
578 	regmatch_t pmatch[1];
579 	return !regexec(&h_regexp[pinfo_re_offset], name, 1, pmatch, 0);
580 #endif
581 }
582 
583 int
yesno(char * prompt,int def)584 yesno(char *prompt, int def)
585 {
586 	char *yes = _("yes");
587 	char *no = _("no");
588 	int key;
589 
590 	attrset(bottomline);
591 	mymvhline(maxy - 1, 0, ' ', maxx);
592 	move(maxy - 1, 0);
593 	/* if default answer is yes */
594 	if (def)
595 		printw("%s([%c]/%c)", prompt, *yes, *no);
596 	else
597 		printw("%s([%c]/%c)", prompt, *no, *yes);
598 	nodelay(stdscr, FALSE);
599 	while (1)
600 	{
601 		key = getch();
602 		if (key == ERR)
603 			return -1;
604 		if (is_enter_key(key))
605 			break;
606 		else
607 		{
608 			if (tolower(key) == tolower(*yes))
609 			{
610 				def = 1;
611 				break;
612 			}
613 			else
614 			{
615 				if (tolower(key) == tolower(*no))
616 				{
617 					def = 0;
618 					break;
619 				}
620 				else
621 					beep();
622 			}
623 		}
624 	}
625 
626 	nodelay(stdscr, TRUE);
627 	if (def)
628 		addstr(yes);
629 	else
630 		addstr(no);
631 	attrset(normal);
632 	return def;
633 }
634 
635 void
myclrtoeol()636 myclrtoeol()
637 {
638 	unsigned x, y;
639 	getyx(stdscr, y, x);
640 	for (unsigned i = x; i < maxx; i++)
641 		mvaddch(y, i, ' ');
642 }
643 
644 void
copy_stripped_from_regexp(char * src,char * dest)645 copy_stripped_from_regexp(char *src, char *dest)
646 {
647 	char *forbidden = "*.\\()[]\n";
648 	while (strchr(forbidden, *src) == NULL)
649 	{
650 		if (*src == 0)
651 			break;
652 		*dest = *src;
653 		src++;
654 		dest++;
655 	}
656 	*dest = 0;
657 }
658 
659 void
myendwin()660 myendwin()
661 {
662 	curs_set(shell_cursor);
663 	endwin();
664 }
665 
666 void
handlewinch()667 handlewinch()
668 {
669 	myendwin();
670 	init_curses();
671 	doupdate();
672 	getmaxyx(stdscr, maxy, maxx);
673 	ungetch(keys.refresh_1);
674 }
675 
676 /*
677  * this functions checks whether the node header node_header
678  * corresponds to node node_name
679  *
680  * e.g. the header is something like:
681  * File: bash.info,  Node: Introduction,  Next: Defs,  Prev: Top,  Up: Top
682  * and we check here if the Node: entry in this header is equal to node_name
683  *
684  * returns  0 if node_header does not belong to a node with name node_name
685  * returns -1 if no checking was done
686  * returns  1 if check turned out ok
687  */
688 int
check_node_name(const char * const node_name,const char * const node_header)689 check_node_name( const char * const node_name, const char * const node_header)
690 {
691 	size_t header_len;
692 	char *header, *str_start, *c;
693 	int res;
694 
695 	/* if either one of node_name or node_header is NULL or a zero
696 	 * sized string, we have nothing to check, so return success */
697 	if ( (node_name==NULL) || (node_header==NULL)
698 		|| (strlen(node_name)==0) || (strlen(node_header)==0) )
699 	{
700 		return 1;
701 	}
702 
703 	header_len = strlen(node_header);
704 
705 	/* copy node_header to a local string which can be mutilated */
706 	/* don't use strdup here, as xmalloc handles all errors */
707 	header = xmalloc( header_len + 1 );
708 	strcpy(header, node_header);
709 
710 	/* search for "Node: foobar," in node_header */
711 	str_start = strstr(header, "Node: ");
712 	if (str_start==NULL) /* no match */
713 	{
714 		return 0;
715 	}
716 	/* advance str_start to the start of the node name */
717 	str_start += strlen("Node: ");
718 	/* and search for the next comma, tab, or newline */
719 	c = str_start;
720 	while ( (*c!=',') && (*c!='\t') && (*c!='\n') && (*c!='\0') ) c++;
721 	*c = '\0';
722 
723 	/* so, now str_start point to a \0-terminated string containing the
724 	 * node name from the header.
725 	 * Let's compare it with the node_name we're looking for */
726 	res = strcmp(str_start, node_name);
727 
728 	/* we're done, so free alloc'ed vars */
729 	xfree(header);
730 
731 	/* check result of strcmp() and return */
732 	if ( res==0 )
733 	{
734 		/* match found */
735 		return 1;
736 	}
737 	else
738 	{
739 		/* no match */
740 		return 0;
741 	}
742 }
743 
744 
745 /*
746  * The wcswidth function returns the number of columns needed to represent
747  * the  wide-character  string pointed to by s, but at most n wide charac‐
748  * ters. If a non-printable wide character occurs among these  characters,
749  * -1 is returned.
750  */
751 #if defined(USE_WCHAR) && !defined(HAVE_WCSWIDTH)
752 int
wcswidth(const wchar_t * wstr,size_t max_len)753 wcswidth(const wchar_t *wstr, size_t max_len)
754 {
755 	int width = 0;
756 	size_t i;
757 	size_t len = wcslen(wstr);
758 
759 	/* never count more than max_len chars */
760 	if (len>max_len) len=max_len;
761 
762 	for (i=0; i<len; i++)
763 	{
764 		if (!iswprint(wstr[i])) return -1;
765 		width += wcwidth(wstr[i]);
766 	}
767 
768 	return width;
769 }
770 #endif /* USE_WCHAR && !HAVE_WCSWIDTH */
771 
772 
773 /* calculcate length of string, handling multibyte strings correctly
774  * returns value <= len
775  */
776 int
width_of_string(const char * const mbs,const int len)777 width_of_string( const char * const mbs, const int len)
778 {
779 	int width;
780 	char *str;
781 #ifdef USE_WCHAR
782 	wchar_t *wstr;
783 #endif /* USE_WCHAR */
784 
785 	if (len<0) return -1;
786 	if (len==0) return 0;
787 
788 	/* copy the string to a local buffer, because we only want to
789 	 * compare the first len bytes */
790 	str = xmalloc(len+1);
791 	memcpy(str, mbs, len);
792 
793 #ifdef USE_WCHAR
794 
795 	/* allocate a widestring */
796 	wstr = xmalloc( (len+1)*sizeof(wchar_t) );
797 
798 	mbstowcs(wstr, str, len);
799 	width = wcswidth(wstr, len);
800 
801 	/* clean up */
802 	xfree(wstr);
803 
804 #else /* USE_WCHAR */
805 
806 	width = strlen(str);
807 
808 #endif /* USE_WCHAR */
809 
810 	/* clean up */
811 	xfree(str);
812 
813 	return width;
814 }
815 
816 /*
817  * calculates the length of string between start and end, counting `\t' as
818  * filling up to 8 chars. (i.e. at line 22 tab will increment the counter by 2
819  * [8-(22-int(22/8)*8)] spaces)
820  */
821 int
calculate_len(char * start,char * end)822 calculate_len(char *start, char *end)
823 {
824 	int len = 0;
825 	char *c = start;
826 	while (c < end)
827 	{
828 		if (*c == '\t')
829 		{
830 			/* now, first count everything leading up to this position */
831 			len += width_of_string(start, c - start);
832 			start = c+1;
833 			/* then add the extra width of the tab */
834 			len = ( len & ~0x07 ) + 0x08;
835 		}
836 		c++;
837 	}
838 	/* then count everything after the last tab */
839 	len += width_of_string(start, c - start);
840 
841 	return len;
842 }
843 
844 /*
845  * create a temporary file in a safe way, and return its name in a newly
846  * allocated string
847  */
848 char *
make_tempfile()849 make_tempfile()
850 {
851 	char *filename;
852 	size_t len;
853 
854 	/* TODO: fix hardcoded /tmp */
855 	char tmpfile_template[] = "/tmp/pinfo.XXXXXX";
856 
857 	/* create a tmpfile */
858 	int fd = mkstemp(tmpfile_template);
859 	/* bug out if it failed */
860 	if (fd == -1)
861 	{
862 		closeprogram();
863 		printf(_("Couldn't open temporary file\n"));
864 		exit(1);
865 	}
866 
867 	/* allocate a new string and copy the filename there */
868 	len = strlen(tmpfile_template)+1;
869 	filename = xmalloc(len+1); /* guarenteerd to be set to \0's */
870 	strncpy(filename, tmpfile_template, len);
871 
872 	/* close the file */
873 	close(fd);
874 
875 	return filename;
876 }
877