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