1 /*========================================================================*\
2 
3 Copyright (c) 1990-2013  Paul Vojta and others
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to
7 deal in the Software without restriction, including without limitation the
8 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 sell copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11 
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14 
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
19 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21 OTHER DEALINGS IN THE SOFTWARE.
22 
23 NOTE:
24 xdvi is based on prior work, as noted in the modification history
25 in xdvi.c.
26 
27 \*========================================================================*/
28 
29 #include "xdvi-config.h"
30 
31 #include <sys/types.h> /* ZLB: must be before sys/socket.h for IRIX 5.3 */
32 #include <sys/socket.h>
33 #include <sys/file.h>	/* this defines FASYNC */
34 #include <sys/ioctl.h>	/* this defines SIOCSPGRP and FIOASYNC */
35 #include <sys/wait.h>	/* this defines WIFEXITED and WEXITSTATUS */
36 
37 #include "xdvi.h"
38 #include "hypertex.h"
39 #include "dvi-init.h"
40 #include "special.h"
41 #include "string-utils.h"
42 
43 #include "kpathsea/tex-file.h"
44 
45 #include "events.h"
46 #include "util.h"
47 #include "x_util.h"
48 #include "message-window.h"
49 #include "search-internal.h"
50 #include "encodings.h"
51 #include "filehist.h"
52 #include "exit-handlers.h"
53 #include "xm_prefs.h" /* for preferences_changed() */
54 
55 #include <errno.h>
56 
57 #ifndef HAVE_MEMICMP
58 #include <ctype.h>
59 #endif
60 
61 #ifdef VMS
62 #include <rmsdef.h>
63 #endif /* VMS */
64 
65 #ifdef	X_NOT_STDC_ENV
66 extern int errno;
67 extern void *malloc();
68 extern void *realloc();
69 #endif
70 
71 
72 #ifdef	DOPRNT	/* define this if vfprintf gives you trouble */
73 #define	vfprintf(stream, message, args)	_doprnt(message, args, stream)
74 #endif
75 
76 #ifdef VMS
77 #include <rmsdef.h>
78 #endif /* VMS */
79 
80 #ifdef	X_NOT_STDC_ENV
81 extern int errno;
82 extern void *malloc();
83 extern void *realloc();
84 #endif
85 
86 #if HAVE_XKB_BELL_EXT
87 # include <X11/XKBlib.h>
88 # define XBell(dpy, percent) XkbBell(dpy, mane.win, percent, (Atom) None)
89 #endif
90 
91 /* if POSIX O_NONBLOCK is not available, use O_NDELAY */
92 #if !defined O_NONBLOCK && defined O_NDELAY
93 # define O_NONBLOCK O_NDELAY
94 #endif
95 
96 /* Linux prefers O_ASYNC over FASYNC; SGI IRIX does the opposite.  */
97 #if !defined(FASYNC) && defined(O_ASYNC)
98 # define FASYNC O_ASYNC
99 #endif
100 
101 #if !defined(FLAKY_SIGPOLL) && !HAVE_STREAMS && !defined(FASYNC)
102 # if !defined(SIOCSPGRP) || !defined(FIOASYNC)
103 #  define FLAKY_SIGPOLL	1
104 # endif
105 #endif
106 
107 #if !FLAKY_SIGPOLL && HAVE_STREAMS
108 # include <stropts.h>
109 
110 # ifndef S_RDNORM
111 #  define S_RDNORM S_INPUT
112 # endif
113 
114 # ifndef S_RDBAND
115 #  define S_RDBAND 0
116 # endif
117 
118 # ifndef S_HANGUP
119 #  define S_HANGUP 0
120 # endif
121 
122 # ifndef S_WRNORM
123 #  define S_WRNORM S_OUTPUT
124 # endif
125 
126 #endif /* not FLAKY_SIGPOLL && HAVE_STREAMS */
127 
128 #include <sys/param.h>
129 #include <sys/stat.h>
130 #include <pwd.h>
131 #include <errno.h>
132 
133 #ifndef MAXSYMLINKS		/* Workaround for Linux libc 4.x/5.x */
134 #define MAXSYMLINKS 5
135 #endif
136 
137 #if HAVE_ICONV_H
138 #include <iconv.h>
139 #endif
140 
141 #include <locale.h>
142 
143 #if USE_LANGINFO
144 #include <langinfo.h>
145 #endif
146 
147 #define BUF_SIZE 1024
148 
149 void
xdvi_assert(const char * version,const char * filename,int lineno,Boolean condition,const char * fmt,...)150 xdvi_assert(const char *version,
151 	    const char *filename,
152 	    int lineno,
153 	    Boolean condition,
154 	    const char *fmt, ...)
155 {
156     if (!(condition)) {
157 	va_list argp;
158 	fprintf(stderr,
159 		"\n************************************************************\n"
160 		"XDvi %s: Failed assertion:\n%s:%d: ",
161 		version, filename, lineno);
162 	va_start(argp, fmt);
163 	(void)vfprintf(stderr, fmt, argp);
164 	va_end(argp);
165 #ifdef NDEBUG
166 	fprintf(stderr,
167 		"\nAborting now. Please report this as a bug to:\n"
168 		"http://sourceforge.net/tracker/?group_id=23164&atid=377580\n"
169 		"If a core dump has been produced, please invoke:\n"
170 		"gdb %s core\nThen type \"bt\", "
171 		"and include the resulting output in your bug report.\n"
172  		"************************************************************\n",
173 		globals.program_name);
174 	do_abort();
175 #else
176 	fprintf(stderr,
177 		"\nPlease report this as a bug to:\n"
178 		"http://sourceforge.net/tracker/?group_id=23164&atid=377580\n"
179  		"************************************************************\n");
180 #endif
181     }
182 }
183 
184 
185 void
xdvi_bell(void)186 xdvi_bell(void)
187 {
188     if (!resource.hush_bell) {
189 	XBell(DISP, 0);
190     }
191 }
192 
193 
194 
195 /* NOTE: keep this table in sync with the #defines in xdvi-debug.h! */
196 struct debug_string_options debug_options[] = {
197     {  DBG_BITMAP,	"bitmap",	", " },
198     {  DBG_DVI,		"dvi",		", " },
199     {  DBG_PK,		"pk",		", " },
200     {  DBG_BATCH,	"batch",	", " },
201     {  DBG_EVENT,	"events",	", " },
202     {  DBG_PS,		"ps",		",\n"},
203     {  DBG_STAT,	"stat",		", " },
204     {  DBG_HASH,	"hash",		", " },
205     {  DBG_OPEN,	"open",		", " },
206     {  DBG_PATHS,	"paths",	", " },
207     {  DBG_EXPAND,	"expand",	", " },
208     {  DBG_SEARCH,	"search",	", " },
209     {  DBG_KPATHSEA,	"kpathsea",	",\n"},
210     {  DBG_HTEX,	"htex",		", " },
211     {  DBG_SRC_SPECIALS,"src",		", " },
212     {  DBG_CLIENT,	"client",	", " },
213     {  DBG_FT,		"ft",		", " },
214     {  DBG_FT_VERBOSE,	"ft_verbose",	",\n"},
215     {  DBG_GUI,		"gui",		", " },
216     {  DBG_FIND,	"find",		", " },
217     {  DBG_FILES,	"files",	", " },
218     {  DBG_PTEXFNT,	"ptexfnt",	", " },
219     {  DBG_ALL,		"all",		"\n" },
220     /* end marker */
221     {  0,		NULL,		NULL }
222 };
223 
224 /*
225  *	General utility routines.
226  */
227 
228 /*
229  * 2-step fopen using close_a_file() if first opening attempt fails with
230  * `too many open files'.
231  */
232 FILE *
try_fopen(const char * fname,const char * mode)233 try_fopen(const char *fname, const char *mode)
234 {
235     FILE *fp = fopen(fname, mode);
236     if (fp == NULL && (errno == EMFILE || errno == ENFILE)) {
237 	close_a_file();
238 	fp = fopen(fname, mode);
239     }
240     return fp;
241 }
242 
243 /*
244  * Like try_fopen(), for fdopen().
245  */
246 FILE *
try_fdopen(int fd,const char * mode)247 try_fdopen(int fd, const char *mode)
248 {
249     FILE *fp = fdopen(fd, mode);
250     if (fp == NULL && (errno == EMFILE || errno == ENFILE)) {
251 	close_a_file();
252 	fp = fdopen(fd, mode);
253     }
254     return fp;
255 }
256 
257 
258 /*
259  * Like try_fopen(), for open().
260  */
261 int
try_open(const char * fname,int flags)262 try_open(const char *fname, int flags)
263 {
264     int fd = open(fname, flags);
265     if (fd < 0 && (errno == EMFILE || errno == ENFILE)) {
266 	close_a_file();
267 	fd = open(fname, flags);
268     }
269     return fd;
270 }
271 
272 /*
273  * Like try_fopen(), for open() with 3 arguments.
274  */
275 int
try_open_mode(const char * fname,int flags,mode_t mode)276 try_open_mode(const char *fname, int flags, mode_t mode)
277 {
278     int fd = open(fname, flags, mode);
279     if (fd < 0 && (errno == EMFILE || errno == ENFILE)) {
280 	close_a_file();
281 	fd = open(fname, flags, mode);
282     }
283     return fd;
284 }
285 
286 
287 /*
288  * This routine should be used for all exits. (SU: This is different
289  * from non-k xdvi, where it's only used for `non-early' exits; all
290  * routines called here should be aware of their own state and either
291  * perform cleanup or just return, unless xdvi_exit() itself checks for
292  * the status).
293  */
294 
295 void
xdvi_exit(int status)296 xdvi_exit(int status)
297 {
298     /* do the following only if the window has been opened: */
299     if (globals.widgets.top_level != 0 && XtIsRealized(globals.widgets.top_level)) {
300 	char *filehist;
301 	file_history_set_page(current_page);
302 	filehist = file_history_get_list();
303 	store_preference(NULL, "fileHistory", "%s", filehist);
304 	free(filehist);
305 
306 #if MOTIF
307 	if (preferences_changed()) {
308 	    return;
309 	}
310 	/*      else { */
311 	/*  	fprintf(stderr, "Preferences not changed.\n"); */
312 	/*      } */
313 #endif
314 	/* try to save user preferences, unless we're exiting with an error */
315 	if (status == EXIT_SUCCESS && !save_user_preferences(True))
316 	    return;
317 
318 	/* Clean up the "xdvi windows" property in the root window.  */
319 	update_window_property(XtWindow(globals.widgets.top_level), False);
320     }
321 
322 #if PS
323     ps_destroy();
324 #endif
325 
326     call_exit_handlers();
327 
328     exit(status);
329 }
330 
331 /*
332  *	invoked on SIGSEGV: try to stop gs before aborting, to prevent gs
333  *	running on with 100% CPU consumption - this can be annoying during
334  *	testing. We'd also like to clean up the "xdvi windows" property in
335  *	the root window, but it might be already too late to talk to the
336  *	X server here (the code can also be triggered by X errors).
337  */
338 void
do_abort(void)339 do_abort(void)
340 {
341 #if PS
342     ps_destroy();
343 #endif
344     /*     if (globals.widgets.top_level) */
345     /* 	exit_clean_windows(); */
346     abort();
347 }
348 
349 /*
350   Expand leading ~ or ~user in path to the actual homedirectory of the user.
351   Returns either NULL if unsuccessful, or result in freshly allocated string.
352   Bugs: ~user doesn't work with NIS/yellow pages.
353 */
354 char *
expand_homedir(const char * path)355 expand_homedir(const char *path)
356 {
357     char *resolved = NULL;
358     const char *orig_path = path;
359     UNUSED(orig_path); /* if HAVE_GETPWNAM or HAVE_GETPWUID && HAVE_GETUID */
360 
361     if (path == NULL)
362 	return NULL;
363 
364     /* if path doesn't start with ~, just return a copy of path */
365     if (*path != '~')
366 	return xstrdup(path);
367 
368     /* expand ~/ or ~user */
369     path++;
370     if (*path == '/') { /* expand `~/' to `$HOME/' */
371 	char *homedir = getenv("HOME");
372 	if (homedir != NULL) {
373 	    resolved = xstrdup(homedir);
374 	    resolved = xstrcat(resolved, path);
375 	}
376 	else {
377 #if defined(HAVE_GETPWUID) && defined(HAVE_GETUID)
378 	    /* homedir not set */
379 	    struct passwd *entry = getpwuid(getuid());
380 	    if (entry != NULL) {
381 		homedir = entry->pw_dir;
382 		if (homedir != NULL) {
383 		    resolved = xstrdup(homedir);
384 		    resolved = xstrcat(resolved, path);
385 		}
386 		else {
387 		    XDVI_ERROR((stderr, "getpwnam returned NULL for entry->pw_dir: %s", strerror(errno)));
388 		    return NULL;
389 		}
390 	    }
391 	    else {
392 		XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno)));
393 		return NULL;
394 	    }
395 #else
396 	    popup_message(globals.widgets.top_level,
397 			  MSG_WARN,
398 			  NULL,
399 			  "$HOME not set, and getpwuid() or getuid() not supported - could not expand \"%s\".",
400 			  orig_path);
401 	    return NULL;
402 #endif
403 	}
404 	TRACE_GUI((stderr, "resolved: |%s|", resolved));
405 	return resolved;
406     }
407     else { /* ~user -> try getpwnam() to get homedir */
408 #ifdef HAVE_GETPWNAM
409 	struct passwd *entry;
410 	char *separator = strchr(path, '/');
411 	char *homedir;
412 
413 	TRACE_GUI((stderr, "separator is: |%s|, len of username: %d",
414 		   separator, (int)(separator - path)));
415 	if (separator == NULL)
416 	    return NULL;
417 
418 	resolved = xmalloc(separator - path + 1);
419 	memcpy(resolved, path, separator - path);
420 	resolved[separator - path] = '\0';
421 
422 	TRACE_GUI((stderr, "username is: |%s|", resolved));
423 	entry = getpwnam(resolved);
424 	if (entry == NULL) {
425 	    XDVI_ERROR((stderr, "getpwnam failed: %s", strerror(errno)));
426 	    return NULL;
427 	}
428 	TRACE_GUI((stderr, "homedir of user is: |%s|", entry->pw_dir));
429 	homedir = entry->pw_dir;
430 
431 	free(resolved);
432 	resolved = xstrdup(homedir);
433 	resolved = xstrcat(resolved, path + (separator - path));
434 	TRACE_GUI((stderr, "resolved: |%s|", resolved));
435 	return resolved;
436 #else
437 	popup_message(globals.widgets.top_level,
438 		      MSG_WARN,
439 		      NULL,
440 		      "Expanding \"%s\" failed: This platform doesn't support getpwnam().",
441 		      orig_path);
442 	return NULL;
443 #endif
444     }
445 
446 }
447 
448 /*
449   realpath implementation if no implementation available.
450   Try to canonicalize path, removing `~', `.' and `..' and expanding symlinks.
451 
452   Adopted from wu-ftpd's fb_realpath (in realpath.c), but without the
453   seteuid(0) stuff (which we don't need, since we never change into
454   directories or read files the user has no permissions for), and
455   without the ugly goto(). Special care has been taken to avoid buffer
456   overflows (e.g. this version is not affected by
457   http://isec.pl/vulnerabilities/isec-0011-wu-ftpd.txt).
458 
459   `resolved' should be a buffer of size MAXPATHLEN.  */
460 
461 char *
my_realpath(const char * path,char * resolved)462 my_realpath(const char *path, char *resolved)
463 {
464     struct stat sb;
465     int n;
466     /*     char *p, *q, *tmp; */
467     char *base;
468     char tmpbuf[MAXPATHLEN];
469     int symlinks = 0;
470 #ifdef HAVE_FCHDIR
471     int fd;
472 #else
473     char cwd[MAXPATHLEN];
474 #endif
475 
476     /* Save cwd for going back later */
477 #ifdef HAVE_FCHDIR
478     if ((fd = try_open(".", O_RDONLY)) < 0)
479 	return NULL;
480 #else /* HAVE_FCHDIR */
481     if (
482 # ifdef HAVE_GETCWD
483 	getcwd(cwd, MAXPATHLEN)
484 # else
485 	getwd(cwd)
486 # endif
487 	== NULL)
488 	return NULL;
489 #endif /* HAVE_FCHDIR */
490 
491     if (strlen(path) + 1 > MAXPATHLEN) {
492 	errno = ENAMETOOLONG;
493 	return NULL;
494     }
495 
496     strcpy(resolved, path);
497 
498     for (;;) { /* loop for resolving symlinks in base name */
499 	/* get base name and dir name components */
500 	char *p = strrchr(resolved, '/');
501 	if (p != NULL) {
502 	    base = p + 1;
503 	    if (p == resolved) {
504 		/* resolved is in root dir; this must be treated as a special case,
505 		   since we can't chop off at `/'; instead, just use the `/':
506 		*/
507 		p = "/";
508 	    }
509 	    else {
510 		/* not in root dir; chop off path name at slash */
511 		while (p > resolved && *p == '/') /* for multiple trailing slashes */
512 		    p--;
513 		*(p + 1) = '\0';
514 		p = resolved;
515 	    }
516 
517 	    /* change into that dir */
518 	    if (chdir(p) != 0)
519 		break;
520 	}
521 	else /* no directory component */
522 	    base = resolved;
523 
524 	/* resolve symlinks or directory names (not used in our case) in basename */
525 	if (*base != '\0') {
526 	    if (
527 #ifdef HAVE_LSTAT
528 		lstat(base, &sb)
529 #else
530 		stat(base, &sb)
531 #endif
532 		== 0) {
533 #ifdef HAVE_LSTAT
534 		if (S_ISLNK(sb.st_mode)) { /* if it's a symlink, iterate for what it links to */
535 		    if (++symlinks > MAXSYMLINKS) {
536 			errno = ELOOP;
537 			break;
538 		    }
539 
540 		    if ((n = readlink(base, resolved, MAXPATHLEN)) < 0)
541 			break;
542 
543 		    resolved[n] = '\0';
544 		    continue;
545 		}
546 #endif /* HAVE_LSTAT */
547 		if (S_ISDIR(sb.st_mode)) { /* if it's a directory, go there */
548 		    if (chdir(base) != 0)
549 			break;
550 
551 		    base = "";
552 		}
553 	    }
554 	}
555 
556 	/* Now get full pathname of current directory and concatenate it with saved copy of base name */
557 	strcpy(tmpbuf, base); /* cannot overrun, since strlen(base) <= strlen(path) < MAXPATHLEN */
558 	if (
559 #ifdef HAVE_GETCWD
560 	    getcwd(resolved, MAXPATHLEN)
561 #else
562 	    getwd(resolved)
563 #endif
564 	    == NULL)
565 	    break;
566 
567 	/* need to append a slash if resolved is not the root dir */
568 	if (!(resolved[0] == '/' && resolved[1] == '\0')) {
569 	    if (strlen(resolved) + 2 > MAXPATHLEN) {
570 		errno = ENAMETOOLONG;
571 		break;
572 	    }
573 	    strcat(resolved, "/");
574 	}
575 
576 	if (*tmpbuf) {
577 	    if (strlen(resolved) + strlen(tmpbuf) + 1 > MAXPATHLEN) {
578 		errno = ENAMETOOLONG;
579 		break;
580 	    }
581 	    strcat(resolved, tmpbuf);
582 	}
583 
584 	/* go back to where we came from */
585 #ifdef HAVE_FCHDIR
586 	(void)fchdir(fd);
587 	close(fd);
588 #else
589 	(void)chdir(cwd);
590 #endif
591 	return resolved;
592     }
593 
594     /* arrive here in case of error: go back to where we came from, and return NULL */
595 #ifdef HAVE_FCHDIR
596     (void)fchdir(fd);
597     close(fd);
598 #else
599     (void)chdir(cwd);
600 #endif
601     return NULL;
602 }
603 
604 
605 #ifndef KPATHSEA
606 
607 /*
608  *	Either (re)allocate storage or fail with explanation.
609  */
610 
611 void *
xmalloc(unsigned size)612 xmalloc(unsigned size)
613 {
614     void *mem = malloc(size);
615 
616     if (mem == NULL)
617 	XDVI_FATAL((stderr, "Out of memory (allocating %u bytes).", size));
618     return mem;
619 }
620 
621 void *
xrealloc(void * where,unsigned size)622 xrealloc(void *where, unsigned size)
623 {
624     void *mem = realloc(where, size);
625 
626     if (mem == NULL)
627 	XDVI_FATAL((stderr, "Out of memory (reallocating %u bytes).", size));
628     return mem;
629 }
630 
631 
632 /*
633  *	Allocate a new string.
634  */
635 
636 char *
xstrdup(const char * str)637 xstrdup(const char *str)
638 {
639     size_t len;
640     char *new;
641 
642     ASSERT(fprintf(stderr, "Test assertion!\n") && 1 == 0);
643     ASSERT(str != NULL, "");
644     len = strlen(str) + 1;
645     new = xmalloc(len);
646     memcpy(new, str, len);
647     return new;
648 }
649 
650 #endif /* not KPATHSEA */
651 
652 
653 /*
654  *	Allocate a new string.  The second argument is the length.
655  */
656 
657 char *
xmemdup(const char * str,size_t len)658 xmemdup(const char *str, size_t len)
659 {
660     char *new;
661 
662     new = xmalloc(len);
663     memcpy(new, str, len);
664     return new;
665 }
666 
667 /* like xstrdup(), but with XtMalloc() */
668 char *
xt_strdup(const char * ptr)669 xt_strdup(const char *ptr)
670 {
671     char *ret = XtMalloc(strlen(ptr) + 1);
672     return strcpy(ret, ptr);
673 }
674 
675 
676 /*
677  * Append str2 to str1, reallocating str1 as neccessary.
678  * Note that this modifies str1, so if you want a clean
679  * new copy, use xstrdup() first, then xstrcat().
680  * str1 should be either NULL, or a valid char * that has
681  * previously been malloc()ed.
682  */
683 
684 char *
xstrcat(char * str1,const char * str2)685 xstrcat(char *str1, const char *str2)
686 {
687     size_t len1 = (str1 == NULL) ? 0 : strlen(str1);
688     size_t len2 = strlen(str2) + 1;
689 
690     str1 = xrealloc(str1, len1 + len2);
691     memcpy(str1 + len1, str2, len2);
692     return str1;
693 }
694 
695 
696 /*
697  *	Allocate bitmap for given font and character
698  */
699 
700 void
alloc_bitmap(struct bitmap * bitmap)701 alloc_bitmap(struct bitmap *bitmap)
702 {
703     unsigned int size;
704 
705     /* fprintf(stderr, "allocating bitmap of size %u x %u\n", bitmap->w, bitmap->h); */
706     /* width must be multiple of <arch-defined> bits for raster_op */
707     bitmap->bytes_wide = ROUNDUP((int)bitmap->w, BMBITS) * BMBYTES;
708     size = bitmap->bytes_wide * bitmap->h;
709     bitmap->bits = xmalloc(size != 0 ? size : 1);
710 }
711 
712 void
clear_bitmap(struct bitmap * bitmap)713 clear_bitmap(struct bitmap *bitmap)
714 {
715     memset(bitmap->bits, 0, bitmap->bytes_wide * bitmap->h);
716 }
717 
718 void
fill_bitmap(struct bitmap * bitmap)719 fill_bitmap(struct bitmap *bitmap)
720 {
721     memset(bitmap->bits, 0xff, bitmap->bytes_wide * bitmap->h);
722 }
723 
724 void
order_reverse_bitmap(struct bitmap * bitmap)725 order_reverse_bitmap(struct bitmap *bitmap)
726 {
727     int i, size = bitmap->bytes_wide * bitmap->h;
728     unsigned char *p = (unsigned char *)bitmap->bits;
729     static unsigned char reverse_byte[0x100];
730 
731     if (reverse_byte[1] == 0) {  /* init reverse_byte[] */
732 	for (i=0; i<0x100; i++) {
733 	    int src, dst = 0;
734 	    for (src=1; src<0x100; src<<=1) {
735 		dst <<= 1;
736 		if (i & src) dst |= 1;
737 	    }
738 	    reverse_byte[i] = dst;
739 	}
740     }
741 
742     for (i=0; i<size; i++) p[i] = reverse_byte[p[i]];
743 }
744 
745 #ifndef HAVE_MEMICMP
746 /*
747  * Case-insensitive version of memcmp().  This code assumes that the second
748  * argument (i.e. what is being compared with) is lower case.
749  */
750 
751 int
memicmp(const char * s1,const char * s2,size_t n)752 memicmp(const char *s1, const char *s2, size_t n)
753 {
754     while (n > 0) {
755 	int i = tolower((int)*s1) - *s2;
756 	if (i != 0)
757 	    return i;
758 	++s1;
759 	++s2;
760 	--n;
761     }
762     return 0;
763 }
764 #endif /* HAVE_MEMICMP */
765 
766 /*
767  * Try to close the pixel file for the least recently used font.
768  * Invoked when we've run out of file descriptors.
769  */
770 void
close_a_file(void)771 close_a_file(void)
772 {
773     struct font *fontp;
774     unsigned short oldest = USHRT_MAX;
775     struct font *f = NULL;
776 
777     if (globals.debug & DBG_OPEN)
778 	puts("Calling close_a_file().");
779 
780     for (fontp = font_head; fontp != NULL; fontp = fontp->next) {
781 	if (fontp->file != NULL && fontp->timestamp <= oldest) {
782 	    f = fontp;
783 	    oldest = fontp->timestamp;
784 	}
785     }
786     /* fprintf(stderr, "oldest = %u\n", oldest); */
787     if (f == NULL)
788 	XDVI_FATAL((stderr, "Can't find an open pixel file to close"));
789     fclose(f->file);
790     f->file = NULL;
791 }
792 
793 /*
794  * Open a file in the given mode. We use XFOPEN since xfopen is already
795  * usurpated by kpathsea's xfopen.c, which just exits rather ungracefully
796  * if it detects a NULL return value; most certainly NOT what we want here.
797  */
798 FILE *
XFOPEN(const char * path,const char * mode)799 XFOPEN(const char *path, const char *mode)
800 {
801     FILE *fp = NULL;
802 #ifdef TESTING_OPEN_FILES
803     fprintf(stderr, "trying to open |%s|\n", path);
804 #endif
805     if ((fp = try_fopen(path, mode)) == NULL && (errno == EMFILE || errno == ENFILE)) {
806 	XDVI_FATAL((stderr, "too many open files"));
807     }
808     return fp;
809 }
810 
811 /*
812  *	Create a pipe, closing a file if necessary.
813  *	We use socketpair() instead of pipe() because several operating
814  *	systems don't support SIGPOLL/SIGIO on pipes:
815  *		SGI IRIX 6.5	F_SETOWN not implemented
816  *		Linux 2.4.2	Not supported
817  */
818 
819 int
xpipe(int * fd)820 xpipe(int *fd)
821 {
822     int	retval;
823 
824     for (;;) {
825 	retval = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
826 	if (retval == 0) { /* success */
827 	    break;
828 	}
829 	if ((errno != EMFILE && errno != ENFILE)) {
830 	    /* failed, but not because of too many files */
831 	    break;
832 	}
833 	close_a_file();
834     }
835     return retval;
836 }
837 
838 
839 
840 /*
841  *
842  *      Read size bytes from the FILE fp, constructing them into a
843  *      signed/unsigned integer.
844  *
845  */
846 
847 unsigned long
get_bytes(FILE * fp,int size)848 get_bytes(FILE *fp, int size)
849 {
850     long x = 0;
851 
852     while (size--)
853 	x = (x << 8) | get_byte(fp);
854     return x;
855 }
856 
857 long
get_lbytes(FILE * fp,int size)858 get_lbytes(FILE *fp, int size)
859 {
860     long x;
861 
862 #if	__STDC__
863     x = (signed char)getc(fp);
864 #else
865     x = (unsigned char)getc(fp);
866     if (x & 0x80)
867 	x -= 0x100;
868 #endif
869     while (--size)
870 	x = (x << 8) | get_byte(fp);
871     return x;
872 }
873 
874 
875 
876 /*
877  *	Create a temporary file and return its fd.  Also put its filename
878  *	in str.  Create str if it's NULL.
879  */
880 
881 #ifndef P_tmpdir
882 #define	P_tmpdir	"/tmp"
883 #endif
884 
885 static const char tmp_suffix[] = "/xdvi-XXXXXX";
886 
887 int
xdvi_temp_fd(char ** str)888 xdvi_temp_fd(char **str)
889 {
890     int fd;
891     char *p;
892     size_t len;
893     static const char *template = NULL;
894 #if !HAVE_MKSTEMP
895     static unsigned long seed;
896     static char letters[] =
897 	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._";
898     char *p1;
899 #endif
900 
901     if (*str != NULL) {
902 	p = *str;
903 
904 	/* O_EXCL is there for security (if root runs xdvi) */
905 	if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1
906 	      && errno == EEXIST))
907 	    return fd;
908 #if HAVE_MKSTEMP
909 	memcpy(p + strlen(p) - 6, "XXXXXX", 6);
910 #endif
911     }
912     else {
913 	if (template == NULL) {
914 	    const char *ourdir;
915 
916 	    ourdir = getenv("TMPDIR");
917 	    if (ourdir == NULL || access(ourdir, W_OK) < 0) {
918 		ourdir = P_tmpdir;
919 		if (access(ourdir, W_OK) < 0)
920 		    ourdir = ".";
921 	    }
922 	    len = strlen(ourdir);
923 	    if (len > 0 && ourdir[len - 1] == '/')
924 		--len;
925 	    template = p = xmalloc(len + sizeof tmp_suffix);
926 	    memcpy(p, ourdir, len);
927 	    memcpy(p + len, tmp_suffix, sizeof tmp_suffix);
928 #if !HAVE_MKSTEMP
929 	    seed = 123456789 * time(NULL) + 987654321 * getpid();
930 #endif
931 	}
932 	*str = p = xstrdup(template);
933     }
934 
935 #if HAVE_MKSTEMP
936     fd = mkstemp(p);
937     if (fd == -1 && (errno == EMFILE || errno == ENFILE)) {
938 	close_a_file();
939 	memcpy(p + strlen(p) - 6, "XXXXXX", 6);
940 	fd = mkstemp(p);
941     }
942 #else
943     p1 = p + strlen(p) - 6;
944     for (;;) {
945 	unsigned long s = ++seed;
946 	char *p2;
947 
948 	for (p2 = p1 + 5; p2 >= p1; --p2) {
949 	    *p2 = letters[s & 63];
950 	    s >>= 6;
951 	}
952 	if (!((fd = try_open_mode(p, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) == -1
953 	      && errno == EEXIST))
954 	    break;
955     }
956 #endif
957     return fd;
958 }
959 
960 
961 /* print a GUI error message for childs that exited with an errorcode */
962 void
handle_child_exit(int status,struct xchild * this)963 handle_child_exit(int status, struct xchild *this)
964 {
965     char *err_msg = NULL;
966 
967     /* if child exited with error and xio struct is available for child,
968        print error text */
969     if (this->io != NULL
970 	&& (WIFEXITED(status) != 0)
971 	&& (WEXITSTATUS(status) != 0)
972 	&& (err_msg = (this->io->read_proc)(this->io->fd, NULL)) != NULL) {
973 
974 	if (this->name == NULL) {
975 	    popup_message(globals.widgets.top_level,
976 			  MSG_WARN,
977 			  NULL,
978 			  err_msg[0] == '\0' ? "An unknown error occurred" : err_msg);
979 	}
980 	else {
981 	    popup_message(globals.widgets.top_level,
982 			  MSG_WARN,
983 			  "Xdvi tries to collect all messages from STDERR. "
984 			  "When no useful error message is available "
985 			  "(e.g. because the program has written to STDOUT instead), "
986 			  "try to run the command again from the command line "
987 			  "to find out what the problem was.",
988 			  "Command \"%s\" exited with error code %d\n%s\n",
989 			  this->name, WEXITSTATUS(status), err_msg);
990 	}
991 	free(err_msg);
992     }
993     free(this->name);
994     free(this->io);
995     free(this);
996 }
997 
998 static void
dummy_write_proc(int fd,void * data)999 dummy_write_proc(int fd, void *data)
1000 {
1001     UNUSED(fd);
1002     UNUSED(data);
1003 
1004     fprintf(stderr, "============== dummy_write_proc called for fd %d\n", fd);
1005 }
1006 
1007 /*
1008  * read what child printed to fd (should be set up to be stderr).
1009  * Allocates and returns error text; caller is responsible for free()ing it
1010  * afterwards.
1011  */
1012 char *
read_child_error(int fd,void * data)1013 read_child_error(int fd, void *data)
1014 {
1015     int bytes = 0, buf_old_size = 0, buf_new_size = 0;
1016     char tmp_buf[BUF_SIZE];
1017     char *err_buf = xstrdup("");
1018     char *ret;
1019     UNUSED(data);
1020 
1021     /* collect stderr messages into err_buf */
1022     while ((bytes = read(fd, tmp_buf, BUF_SIZE - 1)) > 0) {
1023 	buf_old_size = buf_new_size;
1024 	buf_new_size += bytes;
1025 	err_buf = xrealloc(err_buf, buf_new_size + 1);
1026 	memcpy(err_buf + buf_old_size, tmp_buf, buf_new_size - buf_old_size);
1027 	err_buf[buf_new_size] = '\0';
1028     }
1029 
1030     close(fd);
1031     ret = escape_format_arg(err_buf); /* this allocates ret */
1032     free(err_buf);
1033     return ret;
1034 }
1035 
1036 /*
1037  * Fork a child process. If exit_proc is NULL, the process' error messages
1038  * will be collected and a window popped up for GUI display. If exit_proc
1039  * is non-NULL, it should be a function that does something reasonable
1040  * with the error messages itself. It should also free the xchild struct.
1041  *
1042  * If dirname != NULL, the child will chdir into dirname before running the
1043  * command.
1044  */
1045 Boolean
fork_process(const char * proc_name,Boolean redirect_stdout,const char * dirname,childProcT exit_proc,void * data,char * const argv[])1046 fork_process(const char *proc_name, Boolean redirect_stdout,
1047 	     const char *dirname,
1048 	     childProcT exit_proc, void *data,
1049 	     char *const argv[])
1050 {
1051     int i, pid;
1052     struct xchild *my_child = xmalloc(sizeof *my_child);
1053     struct xio *my_io = xmalloc(sizeof *my_io);
1054     int err_pipe[2];
1055     char *volatile buf = xstrdup("");
1056 
1057     for (i = 0; argv[i] != NULL; i++) {
1058 	TRACE_GUI((stderr, "argv: |%s|", argv[i]));
1059 	buf = xstrcat(buf, argv[i]);
1060 	buf = xstrcat(buf, " ");
1061     }
1062 
1063     if (i > 0)
1064 	buf[strlen(buf) - 1] = '\0'; /* chop off trailing space */
1065 
1066     TRACE_GUI((stderr, "forking: |%s|", buf));
1067 
1068     /* flush output buffers to avoid double buffering (i.e. data
1069        waiting in the output buffer being written twice, by the parent
1070        and the child) */
1071     fflush(stdout);
1072     fflush(stderr);
1073 
1074     if (pipe(err_pipe) < 0) {
1075 	XDVI_FATAL((stderr, "pipe error"));
1076     }
1077 
1078     switch (pid = vfork()) {
1079     case -1:	/* forking error */
1080 	perror("vfork");
1081 	close(err_pipe[0]);
1082 	close(err_pipe[1]);
1083 	return False;
1084     case 0:	/* child */
1085 	if (dirname != NULL)
1086 	    (void)chdir(dirname);
1087 	if (globals.debug & DBG_FILES) {
1088 	    char path[MAXPATHLEN];
1089 	    (void)getcwd(path, MAXPATHLEN);
1090 	    fprintf(stderr, "Directory of running `%s': `%s'\n",
1091 		    proc_name, path);
1092 	}
1093 	/* FIXME: There's a bug which prevents this from working
1094 	   with xdvi as child: Whenever xdvi tries to print to stderr,
1095 	   this will hang the child forever. Closing all other file
1096 	   descriptors, as in the #if TRY_FIX regions, seems to fix
1097 	   this, but it also loses all output ...
1098 	*/
1099 #if TRY_FIX
1100 	close(0);
1101 	close(1);
1102 #endif /* TRY_FIX */
1103 	close(err_pipe[0]);	/* no reading from stderr */
1104 
1105 	/* redirect writing to stderr */
1106 	if (dup2(err_pipe[1], STDERR_FILENO) != STDERR_FILENO) {
1107 	    perror("dup2 for stderr");
1108 	    _exit(EXIT_FAILURE);
1109 	    return False;	/* make compiler happy */
1110 	}
1111 	if (redirect_stdout) {
1112 	    /* also redirect writing to stdout */
1113 	    if (dup2(err_pipe[1], STDOUT_FILENO) != STDOUT_FILENO) {
1114 		perror("dup2 for stdout");
1115 		_exit(EXIT_FAILURE);
1116 		return False;	/* make compiler happy */
1117 	    }
1118 	}
1119 
1120 #if TRY_FIX
1121 	/* close all remaining descriptors */
1122 	i = 2;
1123 	while (i < 256 /* TODO: better handling of OPEN_MAX; see Stevens p. 43 */) {
1124 	    close(i++);
1125 	}
1126 #endif /* TRY_FIX */
1127 	execvp(proc_name, argv);
1128 
1129 	/* arrive here only if execvp failed */
1130 	fprintf(stderr, "%s: Execution of %s failed.\n", globals.program_name, proc_name);
1131 	fflush(stderr);
1132 	close(err_pipe[1]);
1133 	_exit(EXIT_FAILURE);
1134 	return False;		/* make compiler happy */
1135     default:	/* parent */
1136 	close(err_pipe[1]);	/* no writing to stderr */
1137 
1138 	my_io->next = NULL;
1139 	my_io->fd = err_pipe[0];
1140 	my_io->xio_events = XIO_IN;
1141 #if HAVE_POLL
1142 	my_io->pfd = NULL;
1143 #endif
1144 	my_io->read_proc = read_child_error;
1145 	my_io->write_proc = dummy_write_proc;
1146 	my_io->data = data;
1147 
1148 	my_child->next = NULL;
1149 	my_child->pid = pid;
1150 	my_child->name = buf;
1151 	my_child->data = data;
1152 	if (exit_proc == NULL) { /* use default exit procedure */
1153 	    my_child->proc = handle_child_exit;
1154 	}
1155 	else {
1156 	    my_child->proc = exit_proc;
1157 	}
1158 	my_child->io = my_io;
1159 
1160 	set_chld(my_child);
1161 
1162 	return True;
1163     }
1164 }
1165 
1166 
1167 
1168 /*
1169  *	Prepare the file descriptor to generate SIGPOLL/SIGIO events.
1170  *	If called with a True argument, set it up for non-blocking I/O.
1171  */
1172 
1173 void
prep_fd(int fd,wide_bool noblock)1174 prep_fd(int fd, wide_bool noblock)
1175 {
1176     /* Set file descriptor for non-blocking I/O */
1177     if (noblock)
1178 	(void) fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
1179 
1180 #if !FLAKY_SIGPOLL
1181 # if HAVE_STREAMS
1182     if (isastream(fd) > 0) {
1183 	if (ioctl(fd, I_SETSIG,
1184 		  S_RDNORM | S_RDBAND | S_HANGUP | S_WRNORM) == -1)
1185 	    perror("xdvi: ioctl I_SETSIG");
1186     }
1187     else
1188 # endif
1189     {
1190 # ifdef FASYNC
1191 	if (fcntl(fd, F_SETOWN, getpid()) == -1)
1192 	    perror("xdvi: fcntl F_SETOWN");
1193 	if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | FASYNC) == -1)
1194 	    perror("xdvi: fcntl F_SETFL");
1195 # elif defined SIOCSPGRP && defined FIOASYNC
1196 	/* For HP-UX B.10.10 and maybe others.  See "man 7 socket".  */
1197 	int arg;
1198 
1199 	arg = getpid();
1200 	if (ioctl(fd, SIOCSPGRP, &arg) == -1)
1201 	    perror("xdvi: ioctl SIOCSPGRP");
1202 	arg = 1;
1203 	if (ioctl(fd, FIOASYNC, &arg) == -1)
1204 	    perror("xdvi: ioctl FIOASYNC");
1205 # endif
1206     }
1207 #endif /* not FLAKY_SIGPOLL */
1208 }
1209 
1210 
1211 /* APS Pointer locator: */
1212 /* Return screen positions */
1213 Boolean
pointerlocate(int * xpos,int * ypos)1214 pointerlocate(int *xpos, int *ypos)
1215 {
1216     Window root, child;
1217     int root_x, root_y;
1218     unsigned int keys_buttons;
1219 
1220     if (!XtIsRealized(globals.widgets.top_level))
1221 	return False;
1222 
1223     return XQueryPointer(DISP, mane.win, &root, &child,
1224 			 &root_x, &root_y, xpos, ypos, &keys_buttons);
1225 }
1226 
1227 unsigned long
parse_debugging_string(const char * arg)1228 parse_debugging_string(const char *arg)
1229 {
1230     int retval = 0;
1231     const char *curr, *last;
1232     size_t i;
1233 
1234     curr = last = arg;
1235 
1236     while (curr != '\0') {
1237 	Boolean matched = False;
1238 
1239 	while (isspace((int)*curr))
1240 	    curr++;
1241 	for (i = 0; debug_options[i].description != NULL; i++) {
1242 	    size_t curr_opt_len = strlen(debug_options[i].description);
1243 	    /* Should we match on length of passed argument, to allow for abbreviations? */
1244 	    if (memicmp(curr,
1245 			debug_options[i].description,
1246 			curr_opt_len) == 0
1247 		&& (curr[curr_opt_len] == '\0'
1248 		    || curr[curr_opt_len] == ','
1249 		    || isspace((int)curr[curr_opt_len]))) {
1250 		matched = True;
1251 		retval |= debug_options[i].bitmap;
1252 		fprintf(stderr, "Debugging option: \"%s\" = \"%s\", debug: %d\n",
1253 			curr, debug_options[i].description, retval);
1254 	    }
1255 	}
1256 	if (!matched) {
1257 	    char *tempstr = xstrdup(curr);
1258 	    char *test;
1259 	    if ((test = strchr(curr, ',')) != NULL) {
1260 		*test = '\0';
1261 	    }
1262 	    XDVI_WARNING((stderr, "Ignoring unknown debugging option \"%s\". Valid options are:\n", tempstr));
1263 	    for (i = 0; debug_options[i].description != NULL; i++) {
1264 		fprintf(stderr, "`%s'%s",
1265 			debug_options[i].description,
1266 			debug_options[i].help_formatting);
1267 	    }
1268 	    fprintf(stderr, "\n");
1269 	    free(tempstr);
1270 	}
1271 	curr = strchr(curr, ',');
1272 	if (curr != NULL)
1273 	    curr++;
1274     }
1275 
1276     return retval;
1277 }
1278 
1279 unsigned long
parse_debugging_option(const char * ptr)1280 parse_debugging_option(const char *ptr)
1281 {
1282     if (ptr == NULL)
1283 	return 0L;
1284     else if (isdigit((int)*ptr)) {
1285 	if (resource.debug_arg == NULL)
1286 	    return DBG_ALL; /* per default debug everything */
1287 	else
1288 	    return strtol(resource.debug_arg, (char **)NULL, 10);
1289     } else if (*ptr == '-')
1290 	return DBG_ALL;
1291     else return parse_debugging_string(ptr);
1292 }
1293 
1294 /* determine average width of a font */
1295 int
get_avg_font_width(XFontStruct * font)1296 get_avg_font_width(XFontStruct *font)
1297 {
1298     int width;
1299 
1300     assert(font != NULL);
1301     width = font->max_bounds.width + font->min_bounds.width / 2;
1302     if (width == 0) {
1303 	/* if min_bounds.width = -max_bounds.width, we probably
1304 	   have a scalable TT font; try to determine its actual
1305 	   width by measuring the letter `x':
1306 	*/
1307 	width = XTextWidth(font, "x", 1);
1308     }
1309     if (width == 0) { /* last resort */
1310 	width = font->max_bounds.width / 2;
1311     }
1312     return width;
1313 
1314 }
1315 
1316 /*
1317   Splits LINE from BEGIN to END (not neccessarily null-terminated)
1318   into items separated by SEP, removes leading or trailing whitespace,
1319   and saves the items as newly allocated char*s into the return array.
1320   Returns the number of items that have been saved as RET_ITEMS.
1321   Empty entries are returned as such, i.e. both `abc:' and `abc: '
1322   return RET_ITEMS = 2 and "" as second entry.
1323 */
1324 char **
split_line(const char * line,char sep,size_t begin,size_t end,size_t * ret_items)1325 split_line(const char *line, char sep, size_t begin, size_t end, size_t *ret_items)
1326 {
1327     const char *c_ptr = line + begin;
1328     const char *e_ptr = line + end;
1329     const char *test_end;
1330 
1331     size_t result_cnt = 0;
1332     size_t alloc_len = 0;
1333     size_t len;
1334     const size_t ALLOC_STEP = 8;
1335     char **result_arr = NULL;
1336 
1337     /* create new result item, resizing result_arr as needed
1338      * (an empty string will coun1 as 1 item: "") */
1339     while (result_cnt + 1 >= alloc_len) {
1340 	alloc_len += ALLOC_STEP;
1341 	result_arr = xrealloc(result_arr, alloc_len * sizeof *result_arr);
1342     }
1343 
1344     while (c_ptr <= e_ptr) {
1345 	/* skip leading whitespace */
1346 	while (c_ptr < e_ptr && isspace((int)*c_ptr)) {
1347 	    c_ptr++;
1348 	}
1349 
1350 	/* find end of current elem, which is either the separator or out of range */
1351 	test_end = strchr(c_ptr, sep);
1352 	/* skip escaped separators */
1353 	while (test_end != NULL && test_end <= e_ptr) {
1354 	    if (test_end > c_ptr && *(test_end - 1) == '\\') {
1355 		test_end = strchr(test_end + 1, sep);
1356 	    }
1357 	    else
1358 		break;
1359 	}
1360 	/* if nothing found, use e_ptr */
1361 	if (test_end == NULL || test_end > e_ptr) {
1362 	    test_end = e_ptr;
1363 	}
1364 
1365 	len = test_end - c_ptr;
1366 
1367 	/* skip trailing whitespace */
1368 	while (len > 0 && isspace((int)c_ptr[len - 1])) {
1369 	    len--;
1370 	}
1371 
1372 	result_arr[result_cnt] = xmalloc(len + 1);
1373 	/* copy into result item, skipping the escape '\\' characters */
1374 	{
1375 	    size_t i = 0, j = 0;
1376 	    while (i < len) {
1377 		if (c_ptr[i] == '\\' && c_ptr[i + 1] == sep) /* i + 1 is safe since (i < len) */
1378 		    i++;
1379 		result_arr[result_cnt][j++] = c_ptr[i++];
1380 	    }
1381 	    result_arr[result_cnt][j] = '\0';
1382 	}
1383 	result_cnt++;
1384 
1385 	/* skip to next item */
1386 	c_ptr = test_end + 1;
1387     }
1388     result_arr[result_cnt] = NULL; /* null-terminate return array, just to be safe */
1389     *ret_items = result_cnt;
1390     return result_arr;
1391 }
1392 
1393 
1394 /*------------------------------------------------------------
1395  *  find_file
1396  *
1397  *  Arguments:
1398  *   filename - absolute or relative file name
1399  *   statbuf  - buffer to stat filename
1400  *   pathinfo - kpse_file_format_type, only used if a kpathsearch for the
1401  *	        file is performed.
1402  *		See  kpathsea/tex-file.h for a list of possible values.
1403  *
1404  *  Returns:
1405  *	 expanded filename
1406  *
1407  *  Purpose:
1408  *	Find a file name corresponding to <filename>, possibly
1409  *	expanding it to a full path name; checks if the file
1410  *	exists by stat()ing it; returns NULL if nothing has
1411  *	been found, else the expanded filename in fresh memory.
1412  *
1413 
1414  *------------------------------------------------------------*/
1415 
1416 char *
find_file(const char * filename,struct stat * statbuf,kpse_file_format_type pathinfo)1417 find_file(const char *filename, struct stat *statbuf, kpse_file_format_type pathinfo)
1418 {
1419     char *tmp;
1420     char *pathname;
1421 
1422     TRACE_SRC((stderr, "checking filename \"%s\"", filename));
1423 
1424     /*
1425      * case 1:
1426      * try absolute filename
1427      */
1428     if (filename[0] == '/') {
1429 	if (stat(filename, statbuf) == 0) {
1430 	    TRACE_SRC((stderr, "Found absolute filename \"%s\"", filename));
1431 	    return xstrdup(filename);
1432 	}
1433 	else {
1434 	    TRACE_SRC((stderr, "Can't stat absolute filename \"%s\"\n", filename));
1435 	    return NULL;
1436 	}
1437     }
1438 
1439     /*
1440      * case 2:
1441      * prepend filename with dir component from the `main' xdvi file (globals.dvi_file.dirname).
1442      * This works for both
1443      * /absolute/path/ + filename
1444      * and
1445      * /absolute/path/ + relative/path/filename
1446      */
1447     ASSERT(globals.dvi_file.dirname != NULL, "globals.dvi_file.dirname should have been initialized");
1448 
1449     pathname = xstrdup(globals.dvi_file.dirname);
1450     pathname = xstrcat(pathname, filename);
1451 
1452     TRACE_SRC((stderr, "Trying globals.dvi_file.dirname: \"%s\"", pathname));
1453     if (stat(pathname, statbuf) == 0) {
1454 	return pathname;
1455     }
1456 
1457     /*
1458      * case 3:
1459      * try current directory; if it's a match, expand to full (but uncanonicalized) path name.
1460      */
1461     if (stat(filename, statbuf) == 0) {
1462 	TRACE_SRC((stderr, "Found file \"%s\" in current dir", filename));
1463 	free(pathname);
1464 	return expand_filename(filename, USE_CWD_PATH);
1465     }
1466 
1467     /*
1468      * case 4a:
1469      * try a kpathsea search for filename, from globals.dvi_file.dirname
1470      */
1471     {
1472 #ifdef HAVE_FCHDIR
1473 	int fd;
1474 #else
1475 	char cwd[MAXPATHLEN];
1476 #endif
1477 
1478 	/* Save cwd for going back later */
1479 	if (
1480 #ifdef HAVE_FCHDIR
1481 	    (fd = try_open(".", O_RDONLY)) >= 0
1482 #else /* HAVE_FCHDIR */
1483 # ifdef HAVE_GETCWD
1484 	    getcwd(cwd, MAXPATHLEN)
1485 # else
1486 	    getwd(cwd)
1487 # endif
1488 	    != NULL
1489 #endif /* HAVE_FCHDIR */
1490 	    ) {
1491 	    if (chdir(globals.dvi_file.dirname) == 0) {
1492 
1493 		TRACE_SRC((stderr,
1494 			   "trying kpathsearch for filename \"%s\" from %s",
1495 			   filename, globals.dvi_file.dirname));
1496 		tmp = kpse_find_file(filename, pathinfo, True);
1497 
1498 		if (tmp != NULL && stat(tmp, statbuf) == 0) {
1499 		    free(pathname);
1500 		    /* go back to where we came from */
1501 #ifdef HAVE_FCHDIR
1502 		    (void)fchdir(fd);
1503 		    close(fd);
1504 #else
1505 		    (void)chdir(cwd);
1506 #endif
1507 		    if (tmp[0] == '/') { /* is it an absolute path? */
1508 			pathname = xstrdup(tmp);
1509 		    }
1510 		    else {
1511 			pathname = xstrdup(globals.dvi_file.dirname);
1512 			pathname = xstrcat(pathname, tmp);
1513 		    }
1514 		    TRACE_SRC((stderr, "Found file: `%s'", pathname));
1515 		    free(tmp);
1516 		    return pathname;
1517 		}
1518 	    }
1519 	    else {
1520 		/*
1521 		 * case 4b:
1522 		 * couldn't change to globals.dvi_file.dirname - try a kpathsea search from CWD
1523 		 */
1524 		TRACE_SRC((stderr,
1525 			   "trying kpathsearch for filename \"%s\" from CWD",
1526 			   filename));
1527 		tmp = kpse_find_file(filename, pathinfo, True);
1528 
1529 		if (tmp != NULL && stat(tmp, statbuf) == 0) {
1530 		    TRACE_SRC((stderr, "Found file: `%s'", tmp));
1531 		    free(pathname);
1532 		    return tmp;
1533 		}
1534 	    }
1535 	}
1536     }
1537     /*
1538      * case 5:
1539      * try a kpathsea search for pathname
1540      */
1541     TRACE_SRC((stderr,
1542 	       "trying kpathsearch for pathname \"%s\"",
1543 	       pathname));
1544     tmp = kpse_find_file(pathname, pathinfo, True);
1545 
1546     if (tmp != NULL && stat(tmp, statbuf) == 0) {
1547 	TRACE_SRC((stderr, "Found file: `%s'", tmp));
1548 	free(pathname);
1549 	return tmp;
1550     }
1551 
1552     /* not found */
1553     free(pathname);
1554     free(tmp);
1555     errno = 0;
1556     return NULL;
1557 }
1558 
1559 /*
1560   Hashtable functions
1561 
1562   The purpose of these is to wrap kpathsea's rather strange hash
1563   functions that can be used to store 2 type of values: char *, and
1564   long, where the latter is interpreted as char *. (See kpathsea/dir.c
1565   for an example of where this is used).
1566 
1567   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1568   BUG ALERT: Note however that a long value may never be removed from
1569   the hash table with hash_delete(), since it would do a strcmp() on
1570   the long interpreted as a pointer!
1571   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1572 
1573   We can't use a different approach (like using a void *) since
1574   the debugging ouput of kpathsea relies on printing the value
1575   either as char * or long (depending on the value of the global flag
1576   `kpse_debug_hash_lookup_int').
1577 */
1578 /*
1579   If key is in hashtable, return True and the integer value in val;
1580   else return False (and leave val untouched).
1581 */
1582 Boolean
find_str_int_hash(hashTableT * hashtable,const char * key,size_t * val)1583 find_str_int_hash(hashTableT *hashtable, const char *key, size_t *val)
1584 {
1585     string *ret;
1586 #ifdef KPSE_DEBUG
1587     if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
1588 	kpse_debug_hash_lookup_int = True;
1589 #endif
1590     ret = hash_lookup(*hashtable, key);
1591 #ifdef KPSE_DEBUG
1592     if (KPSE_DEBUG_P (KPSE_DEBUG_HASH))
1593 	kpse_debug_hash_lookup_int = False;
1594 #endif
1595 
1596     if (ret != NULL) {
1597 	long l = (long)*ret;
1598 	*val = (size_t)l;
1599 	return True;
1600     }
1601     return False;
1602 }
1603 
1604 /*
1605   Insert key-value pair into hashtable. Note that the key is
1606   *not* copied (i.e. it is expected that is had been allocated
1607   somewhere else, and persists throughout the program).
1608 */
1609 void
put_str_int_hash(hashTableT * hashtable,const char * key,size_t val)1610 put_str_int_hash(hashTableT *hashtable, const char *key, size_t val)
1611 {
1612     long ptr = (long)val;
1613     hash_insert(hashtable, key, (const string)ptr);
1614 }
1615 
1616 
1617 #if FREETYPE || PS
1618 
1619 /*
1620  *	General AVL tree mechanism.  Search for a node, and return it if found.
1621  *	Otherwise insert a node.
1622  *	This uses the AVL algorithm from Knuth Vol. 3.
1623  */
1624 
1625 struct avl *
avladd(const char * key,size_t key_len,struct avl ** headp,size_t size)1626 avladd(const char *key, size_t key_len, struct avl **headp, size_t size)
1627 {
1628 	struct avl	*ap;
1629 	struct avl	**app;
1630 	struct avl	*sp;	/* place where rebalancing may be necessary */
1631 	struct avl	**spp;	/* points to sp */
1632 	int		i;
1633 
1634 	/* Search */
1635 	spp = app = headp;
1636 	for (;;) {
1637 	    ap = *app;
1638 	    if (ap == NULL)	/* bottom of tree */
1639 		break;
1640 	    if (ap->bal != 0)
1641 		spp = app;
1642 	    i = key_len - ap->key_len;
1643 	    if (i == 0)
1644 		i = memcmp(key, ap->key, key_len);
1645 	    if (i == 0)		/* found record already */
1646 		return ap;
1647 	    if (i < 0)		/* move left */
1648 		app = &ap->left;
1649 	    else
1650 		app = &ap->right;
1651 	}
1652 
1653 	/* Insert */
1654 	ap = xmalloc(size);
1655 	ap->key = key;
1656 	ap->key_len = key_len;
1657 	ap->bal = 0;
1658 	ap->left = ap->right = NULL;
1659 	*app = ap;
1660 
1661 	/* Adjust balance factors */
1662 	sp = *spp;
1663 	if (sp == ap)
1664 	    return ap;
1665 	i = key_len - sp->key_len;
1666 	if (i == 0)
1667 	    i = memcmp(key, sp->key, key_len);
1668 	sp = (i < 0 ? sp->left : sp->right);
1669 	while (sp != ap) {
1670 	    i = key_len - sp->key_len;
1671 	    if (i == 0)
1672 		i = memcmp(key, sp->key, key_len);
1673 	    if (i < 0) {
1674 		sp->bal = -1;
1675 		sp = sp->left;
1676 	    }
1677 	    else {
1678 		sp->bal = 1;
1679 		sp = sp->right;
1680 	    }
1681 	}
1682 
1683 	/* Balancing act */
1684 	sp = *spp;
1685 	i = key_len - sp->key_len;
1686 	if (i == 0)
1687 	    i = memcmp(key, sp->key, key_len);
1688 	if (i < 0) {
1689 	    if (sp->bal >= 0)
1690 		--sp->bal;
1691 	    else {	/* need to rebalance */
1692 		struct avl *left;
1693 
1694 		left = sp->left;
1695 		if (left->bal < 0) {	/* single rotation */
1696 		    sp->left = left->right;
1697 		    left->right = sp;
1698 		    sp->bal = left->bal = 0;
1699 		    *spp = left;
1700 		}
1701 		else {			/* double rotation */
1702 		    struct avl	*newtop;
1703 
1704 		    newtop = left->right;
1705 		    sp->left = newtop->right;
1706 		    newtop->right = sp;
1707 		    left->right = newtop->left;
1708 		    newtop->left = left;
1709 		    sp->bal = left->bal = 0;
1710 		    if (newtop->bal < 0) ++sp->bal;
1711 		    else if (newtop->bal > 0) --left->bal;
1712 		    newtop->bal = 0;
1713 		    *spp = newtop;
1714 		}
1715 	    }
1716 	}
1717 	else {
1718 	    if (sp->bal <= 0)
1719 		++sp->bal;
1720 	    else {	/* need to rebalance */
1721 		struct avl *right;
1722 
1723 		right = sp->right;
1724 		if (right->bal > 0) {	/* single rotation */
1725 		    sp->right = right->left;
1726 		    right->left = sp;
1727 		    sp->bal = right->bal = 0;
1728 		    *spp = right;
1729 		}
1730 		else {			/* double rotation */
1731 		    struct avl	*newtop;
1732 
1733 		    newtop = right->left;
1734 		    sp->right = newtop->left;
1735 		    newtop->left = sp;
1736 		    right->left = newtop->right;
1737 		    newtop->right = right;
1738 		    sp->bal = right->bal = 0;
1739 		    if (newtop->bal > 0) --sp->bal;
1740 		    else if (newtop->bal < 0) ++right->bal;
1741 		    newtop->bal = 0;
1742 		    *spp = newtop;
1743 		}
1744 	    }
1745 	}
1746 
1747 	return ap;
1748 }
1749 
1750 #endif /* FREETYPE || PS */
1751 
1752 
1753 /* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen */
1754 void
set_dvi_name_expand(const char * new_filename)1755 set_dvi_name_expand(const char *new_filename)
1756 {
1757     ASSERT(new_filename != NULL, "");
1758     free(globals.dvi_name);
1759     globals.dvi_name = expand_filename_append_dvi(new_filename, USE_CWD_PATH, True);
1760 
1761     free(globals.dvi_file.dirname);
1762     globals.dvi_file.dirname = get_dir_component(globals.dvi_name);
1763 
1764     ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component");
1765     globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname);
1766 }
1767 
1768 /* set globals.dvi_name, globals.dvi_file.dirname and globals.dvi_file.dirlen
1769    In contrast to previous function, input filename is not copied.
1770 */
1771 void
set_dvi_name(char * new_filename)1772 set_dvi_name(char *new_filename)
1773 {
1774     ASSERT(new_filename != NULL, "");
1775     free(globals.dvi_name);
1776     globals.dvi_name = new_filename;
1777 
1778     free(globals.dvi_file.dirname);
1779     globals.dvi_file.dirname = get_dir_component(globals.dvi_name);
1780 
1781     ASSERT(globals.dvi_file.dirname != NULL, "dvi_name should be a path with dir component");
1782     globals.dvi_file.dirlen = strlen(globals.dvi_file.dirname);
1783 }
1784 
1785 /*
1786  * Copy the file pointer `in' to the file pointer `out'.  Return True
1787  * if successful, False else (in which case caller should examine
1788  * errno to find the error).
1789  * The caller is responsible for closing the files.
1790  */
1791 Boolean
copy_fp(FILE * in,FILE * out)1792 copy_fp(FILE *in, FILE *out)
1793 {
1794 #define TMP_BUF_SIZE 4 * 1024
1795     char buf[TMP_BUF_SIZE];
1796 
1797     while (feof(in) == 0) {
1798 	size_t bytes_in, bytes_out;
1799 
1800 	bytes_in = fread(buf, 1, TMP_BUF_SIZE, in);
1801 	if (bytes_in < TMP_BUF_SIZE && !feof(in))
1802 	    return False;
1803 	bytes_out = fwrite(buf, 1, bytes_in, out);
1804 	/* 	fprintf(stderr, "read %d, wrote %d bytes\n", bytes_in, bytes_out); */
1805 	if (bytes_out < bytes_in)
1806 	    return False;
1807     }
1808     return True;
1809 
1810 #undef TMP_BUF_SIZE
1811 }
1812 
1813 
1814 /*
1815  * Copy a file from `from_path' to `to'. Return True if successful, False else
1816  * (in which case caller should examine errno to find the error).
1817  */
1818 Boolean
copy_file(const char * from_path,const char * to_path)1819 copy_file(const char *from_path, const char *to_path) {
1820     FILE *from_fp;
1821     FILE *to_fp;
1822 
1823     Boolean retval;
1824 
1825     if ((from_fp = try_fopen(from_path, "rb")) == NULL) {
1826 	XDVI_ERROR((stderr, "opening %s for reading failed: %s", from_path, strerror(errno)));
1827 	return False;
1828     }
1829 
1830     if ((to_fp = try_fopen(to_path, "wb")) == NULL) {
1831 	XDVI_ERROR((stderr, "opening %s for writing failed: %s", to_path, strerror(errno)));
1832 	return False;
1833     }
1834 
1835     retval = copy_fp(from_fp, to_fp);
1836 
1837     fclose(from_fp);
1838     fclose(to_fp);
1839 
1840     return retval;
1841 }
1842 
1843 const char *
get_text_encoding(void)1844 get_text_encoding(void)
1845 {
1846     const char *text_encoding = NULL;
1847 
1848     /* if resource.text_encoding isn't set, use nl_langinfo() if langinfo is available */
1849     if (resource.text_encoding == NULL) {
1850 #if USE_LANGINFO
1851 	if (globals.orig_locale == NULL) {
1852 	    XDVI_ERROR((stderr, "Call to setlocale() returned NULL; assuming ISO-8859-1 charset."));
1853 	    text_encoding = "ISO-8859-1";
1854 	}
1855 	else {
1856 	    if (strcmp(globals.orig_locale, "C") == 0 || strcmp(globals.orig_locale, "POSIX") == 0) {
1857 		/* nl_langinfo returns rather strange values for these ... */
1858 		text_encoding = "ISO-8859-1";
1859 		TRACE_FIND((stderr, "Assuming |%s| for locale |%s|",
1860 			    text_encoding, globals.orig_locale));
1861 	    }
1862 	    else {
1863 		text_encoding = nl_langinfo(CODESET);
1864 		TRACE_FIND((stderr, "nl_langinfo returned: |%s| for locale |%s|",
1865 			    text_encoding, globals.orig_locale));
1866 	    }
1867 	}
1868 #else
1869 	XDVI_WARNING((stderr,
1870 		      "nl_langinfo() not available on this platform, "
1871 		      "and XDvi.textEncoding resource not set; using default "
1872 		      "encoding ISO-8859-1."));
1873 	text_encoding = "ISO-8859-1";
1874 #endif
1875     }
1876     else {
1877 	text_encoding = resource.text_encoding;
1878     }
1879     return text_encoding;
1880 }
1881 
1882 char *
iconv_convert_string(const char * from_enc,const char * to_enc,const char * str)1883 iconv_convert_string(const char *from_enc, const char *to_enc, const char *str)
1884 {
1885     static Boolean have_warned = False;
1886 #if HAVE_ICONV_H
1887     size_t input_len = strlen(str);
1888     size_t conv_len = input_len * 4 + 1; /* worst case ... */
1889     int conv_len_save = conv_len;
1890     char *conv_buf = xmalloc(conv_len);
1891     const char *in_ptr = str;
1892     const char *out_ptr = conv_buf;
1893 
1894     iconv_t conv_desc = iconv_open(to_enc, from_enc);
1895 
1896     if (conv_desc == (iconv_t)(-1)) {
1897 	if (!have_warned) {
1898 	    popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
1899 			  MSG_ERR,
1900 			  NULL,
1901 			  "iconv_open() error: Encoding \"%s\" is not supported by this version of iconv.\n"
1902 			  "Please check the output of \"iconv -l\" and set the X resource\n"
1903 			  "\"XDvi.textEncoding\" to an appropriate value.", from_enc);
1904 	    have_warned = True;
1905 	}
1906 	free(conv_buf);
1907 	return NULL;
1908     }
1909 
1910     TRACE_FIND((stderr, "iconv_convert_string: from `%s', to `%s'", from_enc, to_enc));
1911     if (iconv(conv_desc, (iconv_char_pptrT)&in_ptr, &input_len, (char **)&out_ptr, &conv_len) == (size_t)(-1)) {
1912 	popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
1913 		      MSG_ERR,
1914 		      NULL,
1915 		      "iconv_convert_string(): Could not convert %s to %s: %s.",
1916 		      from_enc, to_enc, strerror(errno));
1917 	iconv_close(conv_desc);
1918 	free(conv_buf);
1919 	return NULL;
1920     }
1921 
1922     iconv_close(conv_desc);
1923     conv_len = conv_len_save - conv_len;
1924     conv_buf[conv_len] = '\0';
1925 
1926     TRACE_FIND((stderr, "after iconv conversion: |%s| %lu bytes\n",
1927 		conv_buf, (unsigned long)conv_len));
1928 
1929 
1930     return conv_buf;
1931 
1932 #else /* HAVE_ICONV_H */
1933 
1934     UNUSED(from_enc);
1935     UNUSED(to_enc);
1936     UNUSED(str);
1937 
1938     /* no iconv available */
1939     if (!have_warned) {
1940 	popup_message(XtNameToWidget(globals.widgets.top_level, "*find_popup"),
1941 		      MSG_ERR,
1942 		      "You can either set the \"LANG\" environment variable or the X resource "
1943 		      "\"XDvi.textEncoding\" to make xdvi use a different language/encoding setting.\n"
1944 		      "Without iconv, only the encodings ISO-8859-1 and UTF-8 are supported. "
1945 		      "For real iconv support, you will need to install the iconv library "
1946 		      "and recompile xdvik.",
1947 		      "Cannot convert from %s to UTF-8 without iconv support compiled in.");
1948 	have_warned = True;
1949     }
1950     return NULL;
1951 
1952 #endif /* HAVE_ICONV_H */
1953 }
1954 
1955 
1956 /* Replace (pseudo-)format arguments in NULL-terminated argv list as follows:
1957  * %f -> filename, %l -> linenumber, %c -> column number.
1958  * If %f or %l are not specified, they are appended as %f and +%l.
1959  * If colno == 0, no %c argument is provided.
1960  */
1961 char **
src_format_arguments(char ** argv,const char * filename,int lineno,int colno)1962 src_format_arguments(char **argv, const char *filename, int lineno, int colno)
1963 {
1964     size_t i;
1965     Boolean found_filename = False;
1966     Boolean found_lineno = False;
1967 
1968     for (i = 0; argv[i] != NULL; i++) {
1969 	char *ptr, *curr = argv[i];
1970 	while ((ptr = strchr(curr, '%')) != NULL) {
1971 	    char *p1;
1972 	    if ((p1 = strchr("flc", ptr[1])) != NULL) { /* we have a formatting char */
1973 		char digit_arg[LENGTH_OF_INT];
1974 		const char *new_elem = NULL;
1975 		/* remember offsets and lengths */
1976 		size_t l_init = ptr - argv[i];
1977 		size_t l_rest = strlen(ptr + 2) + 1;
1978 		size_t l_mid;
1979 
1980 		if (*p1 == 'f') {
1981 		    found_filename = True;
1982 		    new_elem = filename;
1983 		}
1984 		else if (*p1 == 'l') {
1985 		    found_lineno = True;
1986 		    sprintf(digit_arg, "%d", lineno);
1987 		    new_elem = digit_arg;
1988 		}
1989 		else if (*p1 == 'c') {
1990 		    sprintf(digit_arg, "%d", colno);
1991 		    new_elem = digit_arg;
1992 		}
1993 
1994 		l_mid = strlen(new_elem);
1995 
1996 		argv[i] = xrealloc(argv[i], strlen(argv[i]) + l_mid + 1);
1997 		curr = argv[i] + l_init; /* need to reinitialize it because of realloc */
1998 		memmove(curr + l_mid, curr + 2, l_rest);
1999 		memcpy(curr, new_elem, l_mid);
2000 		curr += l_mid;
2001 	    }
2002 	    else if (ptr[1] == '%') { /* escaped %, skip both */
2003 		curr = ptr + 2;
2004 	    }
2005 	    else {
2006 		curr = ptr + 1;
2007 	    }
2008 	}
2009     }
2010 
2011     /* append line number and file name arguments if they were not specified */
2012     if (!found_lineno) {
2013 	i++;
2014 	argv = xrealloc(argv, (i + 1) * sizeof *argv);
2015 	argv[i - 1] = xmalloc(LENGTH_OF_INT + 2);
2016 	sprintf(argv[i - 1], "+%d", lineno);
2017 	argv[i] = NULL;
2018     }
2019 
2020     if (!found_filename) {
2021 	i++;
2022 	argv = xrealloc(argv, (i + 1) * sizeof *argv);
2023 	argv[i - 1] = xstrdup(filename);
2024 	argv[i] = NULL;
2025     }
2026 
2027     return argv;
2028 }
2029 
2030 char *
xstrndup(const char * str,size_t len)2031 xstrndup(const char *str, size_t len)
2032 {
2033     char *new_str = xmalloc(len + 1);
2034     memcpy(new_str, str, len);
2035     new_str[len] = '\0';
2036     return new_str;
2037 }
2038 
2039 #if 0
2040 /*
2041  * Search integer array <arr> of length <arr_len> for for <item>.
2042  * Return the index of the item, or the index of the next smaller item
2043  * if there's no exact match. (That we want the latter is the
2044  * reason why we can't use bsearch()).
2045  */
2046 int
2047 binary_search(int *arr, int arr_len, int item)
2048 {
2049     int lower = -1;
2050     int upper = arr_len;
2051     int mid;
2052 
2053     ASSERT(arr_len >= 1, "binary_search expects arrays of length >= 1");
2054 
2055     do {
2056 	mid = (lower + upper) / 2;
2057 	if (item > arr[mid])
2058 	    lower = mid;
2059 	else if (item < arr[mid])
2060 	    upper = mid;
2061 	else /* exact match */
2062 	    return mid;
2063     } while (upper - lower > 1);
2064 
2065     /* no exact match, return next lower item */
2066     if (mid > 0 && arr[mid] > item)
2067 	return mid - 1;
2068     else
2069 	return mid;
2070 }
2071 #endif /* 0 */
2072