1/*
2
3Copyright (c) 1987, 1988  X Consortium
4
5Permission is hereby granted, free of charge, to any person obtaining
6a copy of this software and associated documentation files (the
7"Software"), to deal in the Software without restriction, including
8without limitation the rights to use, copy, modify, merge, publish,
9distribute, sublicense, and/or sell copies of the Software, and to
10permit persons to whom the Software is furnished to do so, subject to
11the following conditions:
12
13The above copyright notice and this permission notice shall be included
14in all copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22OTHER DEALINGS IN THE SOFTWARE.
23
24Except as contained in this notice, the name of the X Consortium shall
25not be used in advertising or otherwise to promote the sale, use or
26other dealings in this Software without prior written authorization
27from the X Consortium.
28
29*/
30
31/*
32 * xman - X window system manual page display program.
33 * Author:    Chris D. Peterson, MIT Project Athena
34 * Created:   October 27, 1987
35 */
36
37#ifdef HAVE_CONFIG_H
38# include "config.h"
39#endif
40
41#include "globals.h"
42#include "vendor.h"
43#include <X11/Xos.h>            /* sys/types.h and unistd.h included in here */
44#include <sys/stat.h>
45#include <errno.h>
46#include <X11/Xaw/Dialog.h>
47#include <X11/Shell.h>
48
49static FILE *Uncompress(ManpageGlobals * man_globals, const char *filename);
50
51static Boolean UncompressNamed(ManpageGlobals * man_globals,
52                               const char *filename, char *output,
53                               FILE ** output_file);
54static Boolean UncompressUnformatted(ManpageGlobals * man_globals,
55                                     const char *entry, char *filename,
56                                     FILE ** file);
57
58#ifdef HANDLE_ROFFSEQ
59static Boolean ConstructCommand(char *cmdbuf, const char *path,
60                                const char *filename, const char *tempfile);
61#endif
62
63/*	Function Name: PopupWarning
64 *	Description: This function pops up a warning message.
65 *	Arguments: string - the specific warning string.
66 *	Returns: none
67 */
68
69static Widget warnShell, warnDialog;
70
71static void
72PopdownWarning(Widget w, XtPointer client, XtPointer call)
73{
74    XtPopdown((Widget) client);
75}
76
77void
78PopupWarning(ManpageGlobals * man_globals, const char *string)
79{
80    int n;
81    Arg wargs[3];
82    Dimension topX, topY;
83    char buffer[BUFSIZ];
84    Boolean hasPosition;
85
86    snprintf(buffer, sizeof(buffer), "Xman Warning: %s", string);
87    hasPosition = FALSE;
88    if (top) {
89        n = 0;
90        XtSetArg(wargs[n], XtNx, &topX);
91        n++;
92        XtSetArg(wargs[n], XtNy, &topY);
93        n++;
94        XtGetValues(top, wargs, n);
95        hasPosition = TRUE;
96    }
97
98    if (man_globals != NULL)
99        ChangeLabel(man_globals->label, buffer);
100    if (man_globals->label == NULL) {
101        n = 0;
102        if (hasPosition) {
103            XtSetArg(wargs[n], XtNx, topX);
104            n++;
105            XtSetArg(wargs[n], XtNy, topY);
106            n++;
107        }
108        XtSetArg(wargs[n], XtNtransientFor, top);
109        n++;
110        warnShell = XtCreatePopupShell("warnShell", transientShellWidgetClass,
111                                       initial_widget, wargs, n);
112        XtSetArg(wargs[0], XtNlabel, buffer);
113        warnDialog = XtCreateManagedWidget("warnDialog", dialogWidgetClass,
114                                           warnShell, wargs, 1);
115        XawDialogAddButton(warnDialog, "dismiss", PopdownWarning,
116                           (XtPointer) warnShell);
117        XtRealizeWidget(warnShell);
118        Popup(warnShell, XtGrabNone);
119    }
120}
121
122/*	Function Name: PrintError
123 *	Description: This Function prints an error message and exits.
124 *	Arguments: string - the specific message.
125 *	Returns: none. - exits though.
126 */
127
128void
129PrintError(const char *string)
130{
131    fprintf(stderr, "Xman Error: %s\n", string);
132    exit(EXIT_FAILURE);
133}
134
135/*	Function Name: OpenFile
136 *	Description: Assigns a file to the manpage.
137 *	Arguments: man_globals - global structure.
138 *                 file        - the file pointer.
139 *	Returns: none
140 */
141
142void
143OpenFile(ManpageGlobals * man_globals, FILE * file)
144{
145    Arg arglist[1];
146    Cardinal num_args = 0;
147
148    if (man_globals->curr_file) {
149#if 0                           /* Ownership rules need to be fixed first */
150        fclose(man_globals->curr_file);
151#endif
152    }
153    man_globals->curr_file = file;
154
155    XtSetArg(arglist[num_args], XtNfile, man_globals->curr_file);
156    num_args++;
157    XtSetValues(man_globals->manpagewidgets.manpage, arglist, num_args);
158}
159
160
161/*	Function Name: FindManualFile
162 *	Description: Opens the manual page file given the entry information.
163 *	Arguments: man_globals - the globals info for this manpage.
164 *                 section_num - section number of the man page.
165 *                 entry_num   - entry number of the man page.
166 *	Returns: fp - the file pointer
167 *
168 * NOTES:
169 *
170 * If there is a uncompressed section it will look there for uncompressed
171 * manual pages first and then for individually compressed file in the
172 * uncompressed section.
173 *
174 * If there is a compressed directory then it will also look there for
175 * the manual pages.
176 *
177 * If both of these fail then it will attempt to format the manual page.
178 */
179
180FILE *
181FindManualFile(ManpageGlobals * man_globals, int section_num, int entry_num)
182{
183    FILE *file;
184    char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], *temp;
185    char filename[BUFSIZ];
186    const char *entry = manual[section_num].entries[entry_num];
187    int len_cat = strlen(CAT);
188
189    temp = CreateManpageName(entry, 0, 0);
190    snprintf(man_globals->manpage_title, sizeof(man_globals->manpage_title),
191             "The current manual page is: %s.", temp);
192    XtFree(temp);
193
194    ParseEntry(entry, path, section, page);
195
196/*
197 * Look for uncompressed files first.
198 */
199#if defined(__OpenBSD__) || defined(__NetBSD__)
200    /* look in machine subdir first */
201    snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s", path, CAT,
202             section + len_cat, MACHINE, page);
203    if ((file = fopen(filename, "r")) != NULL)
204        return (file);
205#endif
206
207    snprintf(filename, sizeof(filename), "%s/%s%s/%s",
208             path, CAT, section + len_cat, page);
209    if ((file = fopen(filename, "r")) != NULL)
210        return (file);
211
212/*
213 * Then for compressed files in an uncompressed directory.
214 */
215
216#if defined(__OpenBSD__) || defined(__NetBSD__)
217    /* look in machine subdir first */
218    snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s.%s", path, CAT,
219             section + len_cat, MACHINE, page, COMPRESSION_EXTENSION);
220    if ((file = Uncompress(man_globals, filename)) != NULL)
221        return (file);
222#endif
223    snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
224             section + len_cat, page, COMPRESSION_EXTENSION);
225    if ((file = Uncompress(man_globals, filename)) != NULL)
226        return (file);
227#ifdef GZIP_EXTENSION
228    else {
229#if defined(__OpenBSD__) || defined(__NetBSD__)
230        /* look in machine subdir first */
231        snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s.%s", path, CAT,
232                 section + len_cat, MACHINE, page, GZIP_EXTENSION);
233        if ((file = Uncompress(man_globals, filename)) != NULL)
234            return (file);
235#endif
236        snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
237                 section + len_cat, page, GZIP_EXTENSION);
238        if ((file = Uncompress(man_globals, filename)) != NULL)
239            return (file);
240    }
241#endif
242#ifdef BZIP2_EXTENSION
243#if defined(__OpenBSD__) || defined(__NetBSD__)
244    /* look in machine subdir first */
245    snprintf(filename, sizeof(filename), "%s/%s%s/%s/%s.%s", path, CAT,
246             section + len_cat, MACHINE, page, BZIP2_EXTENSION);
247    if ((file = Uncompress(man_globals, filename)) != NULL)
248        return (file);
249#endif
250    {
251        snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
252                 section + len_cat, page, BZIP2_EXTENSION);
253        if ((file = Uncompress(man_globals, filename)) != NULL)
254            return (file);
255    }
256#endif
257#ifdef LZMA_EXTENSION
258    {
259        snprintf(filename, sizeof(filename), "%s/%s%s/%s.%s", path, CAT,
260                 section + len_cat, page, LZMA_EXTENSION);
261        if ((file = Uncompress(man_globals, filename)) != NULL)
262            return (file);
263    }
264#endif
265
266/*
267 * And lastly files in a compressed directory.
268 *
269 * The directory is not actually compressed it is just named man#.Z
270 * and all files in it are compressed without the .Z extension.
271 * HP does it this way (really :-).
272 */
273
274    snprintf(filename, sizeof(filename), "%s/%s%s.%s/%s", path, CAT,
275             section + len_cat, COMPRESSION_EXTENSION, page);
276    if ((file = Uncompress(man_globals, filename)) != NULL)
277        return (file);
278/*
279 * We did not find any preformatted manual pages, try to format it.
280 */
281
282    return (Format(man_globals, entry));
283}
284
285#ifndef HAVE_MKSTEMP
286/* Emulate mkstemp to allow use of a common API in the many calls below */
287_X_HIDDEN int
288Xmkstemp (char *template)
289{
290    int fd = 0;
291    char tmp[PATH_MAX];
292
293    if (strlen(template) >= sizeof(tmp))
294        return -1;
295    /* save copy of unmodified template in case we have to try again */
296    strcpy(tmp, template);
297
298    do {
299        if (fd == -1)
300            strcpy(template, tmp);
301        if ((mktemp(template) == NULL) || (template[0] == '\0'))
302            return -1;
303        fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
304    } while ((fd == -1) && (errno == EEXIST || errno == EINTR));
305
306    return fd;
307}
308#endif
309
310/*	Function Namecompress
311 *	Description: This function will attempt to find a compressed man
312 *                   page and uncompress it.
313 *	Arguments: man_globals - the pseudo global info.
314 *                 filename - name of file to uncompress.
315 *	Returns:; a pointer to the file or NULL.
316 */
317
318static FILE *
319Uncompress(ManpageGlobals * man_globals, const char *filename)
320{
321    char tmp_file[BUFSIZ];
322    FILE *file;
323
324    if (!UncompressNamed(man_globals, filename, tmp_file, &file)) {
325        PopupWarning(man_globals, "Something went wrong in retrieving the "
326                     "uncompressed manual page try cleaning up /tmp.");
327        return (NULL);
328    }
329
330    remove(tmp_file);           /* remove name in tree, it will remain
331                                   until we close the fd, however. */
332    return (file);
333}
334
335/*	Function Name: UncompressNamed
336 *	Description: This function will attempt to find a compressed man
337 *                   page and uncompress it.
338 *	Arguments: man_globals - the pseudo global info.
339 *                 filename - name of file to uncompress.
340 * RETURNED        output - the file name output (must be an allocated string).
341 *	Returns:; TRUE if the file was found.
342 */
343
344static Boolean
345UncompressNamed(ManpageGlobals * man_globals, const char *filename,
346                char *output, FILE ** output_file)
347{
348    char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
349    struct stat junk;
350    int fd, omask;
351
352    if (stat(filename, &junk) != 0) {   /* Check for existence of the file. */
353        if (errno != ENOENT) {
354            snprintf(error_buf, sizeof(error_buf),
355                     "Error while stating file %s, errno = %d", filename,
356                     errno);
357            PopupWarning(man_globals, error_buf);
358        }
359        return (FALSE);
360    }
361
362/*
363 * Using stdin is necessary to fool zcat since we cannot guarantee
364 * the .Z extension.
365 */
366
367    strcpy(tmp, MANTEMP);       /* get a temp file. */
368    omask = umask(077);
369    fd = mkstemp(tmp);
370    umask(omask);
371    if (fd < 0) {
372        PopupWarning(man_globals, "Error creating a temp file");
373        return FALSE;
374    }
375    *output_file = fdopen(fd, "r");
376    if (*output_file == NULL) {
377        remove(tmp);
378        close(fd);
379        PopupWarning(man_globals, "Error opening temp file");
380        return FALSE;
381    }
382    strcpy(output, tmp);
383
384#ifdef GZIP_EXTENSION
385    if (streq(filename + strlen(filename) - strlen(GZIP_EXTENSION),
386              GZIP_EXTENSION))
387        snprintf(cmdbuf, sizeof(cmdbuf), GUNZIP_FORMAT, filename, output);
388    else
389#endif
390#ifdef BZIP2_EXTENSION
391    if (streq(filename + strlen(filename) - strlen(BZIP2_EXTENSION),
392                  BZIP2_EXTENSION))
393        snprintf(cmdbuf, sizeof(cmdbuf), BUNZIP2_FORMAT, filename, output);
394    else
395#endif
396#ifdef LZMA_EXTENSION
397    if (streq(filename + strlen(filename) - strlen(LZMA_EXTENSION),
398                  LZMA_EXTENSION))
399        snprintf(cmdbuf, sizeof(cmdbuf), UNLZMA_FORMAT, filename, output);
400    else
401#endif
402        snprintf(cmdbuf, sizeof(cmdbuf), UNCOMPRESS_FORMAT, filename, output);
403    if (system(cmdbuf) == 0)    /* execute search. */
404        return (TRUE);
405
406    snprintf(error_buf, sizeof(error_buf),
407             "Error while uncompressing, command was: %s", cmdbuf);
408    PopupWarning(man_globals, error_buf);
409    return (FALSE);
410}
411
412#if defined(SMAN) && defined(SFORMAT)
413/*	Function Name: SgmlToRoffNamed
414 *	Description: This function will attempt to find an SGML man
415 *                   page and convert it to roff format.
416 *	Arguments: man_globals - the pseudo global info.
417 *                 filename - name of file to uncompress.
418 * RETURNED        output - the file name output (must be an allocated string).
419 *	Returns:; TRUE if the file was found.
420 */
421
422static Boolean
423SgmlToRoffNamed(ManpageGlobals * man_globals, char *filename, char *output,
424                FILE ** output_file)
425{
426    char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
427    struct stat junk;
428    int fd, omask;
429
430    if (stat(filename, &junk) != 0) {   /* Check for existence of the file. */
431        if (errno != ENOENT) {
432            snprintf(error_buf, sizeof(error_buf),
433                     "Error while stating file %s, errno = %d", filename,
434                     errno);
435            PopupWarning(man_globals, error_buf);
436        }
437        return (FALSE);
438    }
439
440    strcpy(tmp, MANTEMP);       /* get a temp file. */
441    omask = umask(077):
442    fd = mkstemp(tmp);
443    umask(omask);
444    if (fd < 0) {
445        PopupWarning(man_globals, "Error creating a temp file");
446        return FALSE;
447    }
448    *output_file = fdopen(fd, "r");
449    if (*output_file == NULL) {
450        remove(tmp);
451        close(fd);
452        PopupWarning(man_globals, "Error opening temp file");
453        return FALSE;
454    }
455    strcpy(output, tmp);
456
457    snprintf(cmdbuf, sizeof(cmdbuf), "%s %s >> %s", SFORMAT, filename, output);
458    if (system(cmdbuf) == 0)    /* execute search. */
459        return (TRUE);
460
461    snprintf(error_buf, sizeof(error_buf),
462             "Error while converting from sgml, command was: %s", cmdbuf);
463    PopupWarning(man_globals, error_buf);
464    return (FALSE);
465}
466#endif                          /* defined (SMAN) && defined(SFORMAT) */
467
468/*	Function Name: Format
469 *	Description: This function formats the manual pages and interfaces
470 *                   with the user.
471 *	Arguments: man_globals - the pseudo globals
472 *                 file - the file pointer to use and return
473 *                 entry - the current entry struct.
474 *                 current_box - The current directory being displayed.
475 *	Returns: none.
476 */
477
478/* ARGSUSED */
479FILE *
480Format(ManpageGlobals * man_globals, const char *entry)
481{
482    FILE *file = NULL;
483    int fd, omask;
484
485    Widget manpage = man_globals->manpagewidgets.manpage;
486    char cmdbuf[BUFSIZ], tmp[BUFSIZ], filename[BUFSIZ], error_buf[BUFSIZ];
487    char path[BUFSIZ], sect[BUFSIZ];
488    XEvent event;
489    Position x, y;              /* location to pop up the
490                                   "would you like to save" widget. */
491
492    if (!UncompressUnformatted(man_globals, entry, filename, &file)) {
493        /* We Really could not find it, this should never happen, yea right. */
494        snprintf(error_buf, sizeof(error_buf),
495                 "Could not open manual page, %s", entry);
496        PopupWarning(man_globals, error_buf);
497        XtPopdown(XtParent(man_globals->standby));
498        return (NULL);
499    }
500
501    if (file != NULL) {
502        char line[BUFSIZ];
503
504        if (fgets(line, sizeof(line), file) != NULL) {
505            if (strncmp(line, ".so ", 4) == 0) {
506                size_t len = strlen(line); /* must be >= 4 to pass strncmp */
507                if (line[len - 1] == '\n')
508                    line[len - 1] = '\0';
509                fclose(file);
510                remove(filename);
511                if (line[4] != '/') {
512                    char *ptr = NULL;
513
514                    strcpy(tmp, entry);
515                    if ((ptr = strrchr(tmp, '/')) != NULL) {
516                        *ptr = '\0';
517                        if ((ptr = strrchr(tmp, '/')) != NULL)
518                            ptr[1] = '\0';
519                    }
520                }
521                else
522                    *tmp = '\0';
523                snprintf(filename, sizeof(filename), "%s%s", tmp, line + 4);
524
525                return (Format(man_globals, filename));
526            }
527        }
528        fclose(file);
529    }
530
531    Popup(XtParent(man_globals->standby), XtGrabExclusive);
532    while (!XCheckTypedWindowEvent(XtDisplay(man_globals->standby),
533                                   XtWindow(man_globals->standby),
534                                   Expose, &event));
535    XtDispatchEvent(&event);
536    XFlush(XtDisplay(man_globals->standby));
537
538    strcpy(tmp, MANTEMP);       /* Get a temp file. */
539    omask = umask(077);
540    fd = mkstemp(tmp);
541    umask(omask);
542    if (fd >= 0) {
543        file = fdopen(fd, "r");
544        if (file == NULL) {
545            remove(tmp);
546            close(fd);
547        }
548    }
549    else
550        file = NULL;
551    if (file == NULL) {
552        PopupWarning(man_globals, "Something went wrong in opening the "
553                     "temp file, try cleaning up /tmp");
554        return NULL;
555    }
556    strcpy(man_globals->tempfile, tmp);
557
558    ParseEntry(entry, path, sect, NULL);
559
560#ifndef HANDLE_ROFFSEQ
561    snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s >> %s %s", path, TBL,
562             filename, FORMAT, man_globals->tempfile, "2> /dev/null");
563#else
564    /* Handle more flexible way of specifying the formatting pipeline */
565    if (!ConstructCommand(cmdbuf, path, filename, man_globals->tempfile)) {
566        PopupWarning(man_globals, "Constructed command was too long!");
567        fclose(file);
568        file = NULL;
569    }
570    else
571#endif                          /* HANDLE_ROFFSEQ */
572
573    if (system(cmdbuf) != 0) {  /* execute search. */
574        snprintf(error_buf, sizeof(error_buf),
575                 "Something went wrong trying to run the command: %s", cmdbuf);
576        PopupWarning(man_globals, error_buf);
577        fclose(file);
578        file = NULL;
579    }
580    else {
581        if (file != NULL) {
582            XtPopdown(XtParent(man_globals->standby));
583
584            if ((man_globals->save == NULL) ||
585                (man_globals->manpagewidgets.manpage == NULL))
586                remove(man_globals->tempfile);
587            else {
588                char *ptr, catdir[BUFSIZ];
589
590                /*
591                 * If the catdir is writable then ask the user if he/she wants to
592                 * write the man page to it.
593                 */
594
595                strcpy(catdir, man_globals->save_file);
596                if ((ptr = strrchr(catdir, '/')) != NULL) {
597                    *ptr = '\0';
598
599                    if (access(catdir, W_OK) != 0)
600                        remove(man_globals->tempfile);
601                    else {
602                        x = (Position) Width(man_globals->manpagewidgets.
603                                             manpage) / 2;
604                        y = (Position) Height(man_globals->manpagewidgets.
605                                              manpage) / 2;
606                        XtTranslateCoords(manpage, x, y, &x, &y);
607                        PositionCenter(man_globals->save, (int) x, (int) y, 0,
608                                       0, 0, 0);
609                        XtPopup(man_globals->save, XtGrabExclusive);
610                    }
611                }
612                else
613                    remove(man_globals->tempfile);
614            }
615        }
616    }
617
618    /*
619     * If the original was compressed or in another format, delete temporary file.
620     */
621    if (man_globals->deletetempfile)
622        remove(filename);
623
624    return (file);
625}
626
627#ifdef HANDLE_ROFFSEQ
628/*      Function Name: ConstructCommand
629 *      Description: Constructs the pipeline of commands necessary to format
630 *                   a manual page.
631 *      Arguments: cmdbuf - the buffer into which to write the command
632 *                 path - the directory in which the original man page resides
633 *                 filename - the (uncompressed) manpage source file
634 *                 tempfile - the name of a temporary file to direct the final
635 *                  output of the pipeline into
636 *      Returns: TRUE if the command fit into the buffer, FALSE if it would
637 *               be too long (more than BUFSIZ characters)
638 */
639static Boolean
640ConstructCommand(char *cmdbuf, const char *path,
641                 const char *filename, const char *tempfile)
642{
643#ifdef HAVE_MANDB
644    int used = snprintf(cmdbuf, BUFSIZ, "MAN_KEEP_FORMATTING=x GROFF_NO_SGR= "
645                        "man -l %s > %s 2>/dev/null",
646                        filename, tempfile);
647    if (used >= BUFSIZ - 1)
648	return FALSE;
649    return TRUE;
650#else
651    /* The original code did the following to produce a command line:
652     *   sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
653     *      filename, FORMAT, man_globals->tempfile, "2> /dev/null");
654     * We are more flexible and follow more or less the algorithm used
655     * by the Linux man command:
656     *  + Obtain a string of letters from the following sources in order
657     *    of preference:
658     *    + a command line option (not implemented in xman; it's probably not
659     *      useful)
660     *    + the first line of the manpage source, if it is of the form:
661     *      '\" <string>
662     *    + the MANROFFSEQ environment variable
663     *    + a default string; this is "".
664     *  + Interpret the string as a pipeline of filters:
665     *    + e = eqn   g = grap   p = pic   t = tbl   v = vgrind   r = refer
666     *  + zsoelim is always run as the first preprocessor in any case.
667     *
668     * Strictly speaking we should save a catpage iff the string comes
669     * from the file or is the default.
670     *
671     * You'll notice that we format a man page into ASCII text output and then
672     * attempt to interpret things like L^HL as bold and so forth. This
673     * is so obviously the Wrong Thing it's untrue.
674     */
675    char *c = cmdbuf;           /* current posn in buffer */
676    int left = BUFSIZ;          /* space left in buffer */
677    int used;
678    const char *fmt;
679    char fmtbuf[128];
680
681    fmt = NULL;
682    /* If you have a command line option that gives a setting for fmt,
683       set it here. */
684
685    if (!fmt) {
686        /* This is the tricky bit: extract a format string from the source file
687         * Annoyingly, filename might be relative or absolute. We cheat and
688         * use system to get the thing to a known absolute filename.
689         */
690        FILE *file;
691        int gotfmt = 0;    /* set to 1 if we got a directive from source */
692        char fname[PATH_MAX];
693
694        if (filename[0] == '/') {
695            snprintf(fname, sizeof(fname), "%s", filename);
696        }
697        else {
698            snprintf(fname, sizeof(fname), "%s/%s", path, filename);
699        }
700        if ((file = fopen(fname, "r")) != NULL) {
701            if ((fgets(fmtbuf, sizeof(fmtbuf), file)) &&
702                (!memcmp(fmtbuf, "'\\\" ", 4))) {
703                /* that's squote-backslash-dquote-space */
704                int len = strlen(fmtbuf);
705
706                if (len && (fmtbuf[len - 1] == '\n')) {
707                    fmtbuf[len - 1] = 0;
708                    fmt = fmtbuf + 3;
709                    gotfmt++;
710                }
711            }
712            fclose(file);
713        }
714        if (!gotfmt) {          /* not there or some error */
715            fmt = getenv("MANROFFSEQ");
716        }
717    }
718
719    if (!fmt) {
720        fmt = DEFAULT_MANROFFSEQ;
721    }
722
723    /* Start with the first fixed part of the command line */
724    used = snprintf(c, left, "cd %s; %s %s ", path, ZSOELIM, filename);
725    left -= used;
726    c += used;
727    if (left <= 1)
728        return (FALSE);
729
730    /* Now add preprocessors of the form '| processor' */
731    for (; *fmt; fmt++) {
732        const char *filter;
733
734        switch (*fmt) {
735        case 'e':
736            filter = EQN;
737            break;
738        case 'g':
739            filter = GRAP;
740            break;
741        case 'p':
742            filter = ROFF_PIC;
743            break;
744        case 't':
745            filter = TBL;
746            break;
747        case 'v':
748            filter = VGRIND;
749            break;
750        case 'r':
751            filter = REFER;
752            break;
753        default:
754            filter = NULL;
755            break;
756        }
757        if (filter) {
758            used = snprintf(c, left, " | %s ", filter);
759            left -= used;
760            c += used;
761            if (left <= 1)
762                return (FALSE);
763        }
764    }
765
766    /* Now add the fixed trailing part 'formatprog > tempfile 2> /dev/null' */
767    used = snprintf(c, left, " | %s >> %s 2>/dev/null", FORMAT, tempfile);
768    left -= used;
769    if (left <= 1)
770        return (FALSE);
771
772    return (TRUE);
773#endif /* man-db */
774}
775#endif                          /* HANDLE_ROFFSEQ */
776
777/*	Function Name: UncompressUnformatted
778 *	Description: Finds an uncompressed unformatted manual page.
779 *	Arguments: man_globals - the pseudo global structure.
780 *                 entry - the manual page entry.
781 * RETURNED        filename - location to put the name of the file.
782 *	Returns: TRUE if the file was found.
783 */
784
785static Boolean
786UncompressUnformatted(ManpageGlobals * man_globals, const char *entry,
787                      char *filename, FILE ** file)
788{
789    char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
790    int len_cat = strlen(CAT), len_man = strlen(MAN);
791
792#if defined(SMAN) && defined(SFORMAT)
793    int len_sman = strlen(SMAN);
794#endif
795
796    ParseEntry(entry, path, section, page);
797
798    man_globals->bzip2 = FALSE;
799    man_globals->lzma = FALSE;
800
801#if defined(__OpenBSD__) || defined(__NetBSD__)
802    /*
803     * look for uncompressed file in machine subdir first
804     */
805    snprintf(filename, BUFSIZ, "%s/%s%s/%s/%s", path, MAN,
806             section + len_cat, MACHINE, page);
807    if (access(filename, R_OK) == 0) {
808        man_globals->compress = FALSE;
809        man_globals->gzip = FALSE;
810        man_globals->deletetempfile = FALSE;
811        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
812                 "%s/%s%s/%s/%s", path, CAT, section + len_cat, MACHINE, page);
813        return (TRUE);
814    }
815    /*
816     * Then for compressed files in an uncompressed directory.
817     */
818    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
819    if (UncompressNamed(man_globals, input, filename, file)) {
820        man_globals->compress = TRUE;
821        man_globals->deletetempfile = TRUE;
822        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
823                 "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
824                 COMPRESSION_EXTENSION);
825        return (TRUE);
826    }
827#ifdef GZIP_EXTENSION
828    else {
829        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
830        if (UncompressNamed(man_globals, input, filename, file)) {
831            man_globals->compress = TRUE;
832            man_globals->gzip = TRUE;
833            man_globals->deletetempfile = TRUE;
834            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
835                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
836                     GZIP_EXTENSION);
837            return (TRUE);
838        }
839    }
840#endif                          /* GZIP_EXTENSION */
841#endif                          /* __OpenBSD__ || __NetBSD__ */
842
843#ifdef BZIP2_EXTENSION
844    {
845        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
846        if (UncompressNamed(man_globals, input, filename, file)) {
847            man_globals->compress = TRUE;
848            man_globals->gzip = FALSE;
849            man_globals->bzip2 = TRUE;
850            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
851                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
852                     BZIP2_EXTENSION);
853            return (TRUE);
854        }
855    }
856#endif                          /* BZIP2_EXTENSION */
857
858#ifdef LZMA_EXTENSION
859    {
860        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
861        if (UncompressNamed(man_globals, input, filename, file)) {
862            man_globals->compress = TRUE;
863            man_globals->gzip = FALSE;
864            man_globals->lzma = TRUE;
865            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
866                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
867                     LZMA_EXTENSION);
868            return (TRUE);
869        }
870    }
871#endif                          /* LZMA_EXTENSION */
872
873/*
874 * Look for uncompressed file first.
875 */
876
877    snprintf(filename, BUFSIZ, "%s/%s%s/%s", path, MAN, section + len_man,
878             page);
879    if (access(filename, R_OK) == 0) {
880        man_globals->compress = FALSE;
881        man_globals->gzip = FALSE;
882        man_globals->deletetempfile = FALSE;
883        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
884                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
885        return (TRUE);
886    }
887
888#if defined(SMAN) && defined(SFORMAT)
889    /*
890     * Look for uncompressed sgml file next.
891     */
892
893    snprintf(input, BUFSIZ, "%s/%s%s/%s", path, SMAN, section + len_sman, page);
894    if (SgmlToRoffNamed(man_globals, input, filename, file)) {
895        man_globals->compress = FALSE;
896        man_globals->gzip = FALSE;
897        man_globals->deletetempfile = TRUE;
898        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
899                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
900        return (TRUE);
901    }
902#endif
903
904/*
905 * Then for compressed files in an uncompressed directory.
906 */
907
908    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
909    if (UncompressNamed(man_globals, input, filename, file)) {
910        man_globals->compress = TRUE;
911        man_globals->deletetempfile = TRUE;
912        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
913                 "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
914                 COMPRESSION_EXTENSION);
915        return (TRUE);
916    }
917#ifdef GZIP_EXTENSION
918    else {
919        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
920        if (UncompressNamed(man_globals, input, filename, file)) {
921            man_globals->compress = TRUE;
922            man_globals->gzip = TRUE;
923            man_globals->deletetempfile = TRUE;
924            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
925                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
926                     GZIP_EXTENSION);
927            return (TRUE);
928        }
929    }
930#endif
931
932#ifdef BZIP2_EXTENSION
933    {
934        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
935        if (UncompressNamed(man_globals, input, filename, file)) {
936            man_globals->compress = TRUE;
937            man_globals->gzip = TRUE;
938            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
939                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
940                     BZIP2_EXTENSION);
941            return (TRUE);
942        }
943    }
944#endif
945
946#ifdef LZMA_EXTENSION
947    {
948        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
949        if (UncompressNamed(man_globals, input, filename, file)) {
950            man_globals->compress = TRUE;
951            man_globals->lzma = TRUE;
952            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
953                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
954                     LZMA_EXTENSION);
955            return (TRUE);
956        }
957    }
958#endif
959
960/*
961 * And lastly files in a compressed directory.
962 */
963
964    snprintf(input, sizeof(input), "%s/%s%s.%s/%s", path,
965             MAN, section + len_man, COMPRESSION_EXTENSION, page);
966    if (UncompressNamed(man_globals, input, filename, file)) {
967        man_globals->compress = TRUE;
968        man_globals->deletetempfile = TRUE;
969        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
970                 "%s/%s%s.%s/%s", path, CAT, section + len_cat,
971                 COMPRESSION_EXTENSION, page);
972        return (TRUE);
973    }
974    return (FALSE);
975}
976
977/*	Function Name: AddCursor
978 *	Description: This function adds the cursor to the window.
979 *	Arguments: w - the widget to add the cursor to.
980 *                 cursor - the cursor to add to this widget.
981 *	Returns: none
982 */
983
984void
985AddCursor(Widget w, Cursor cursor)
986{
987    XColor colors[2];
988    Arg args[10];
989    Cardinal num_args = 0;
990    Colormap c_map;
991
992    if (!XtIsRealized(w)) {
993        PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
994        return;
995    }
996
997    XtSetArg(args[num_args], XtNcolormap, &c_map);
998    num_args++;
999    XtGetValues(w, args, num_args);
1000
1001    colors[0].pixel = resources.cursors.fg_color;
1002    colors[1].pixel = resources.cursors.bg_color;
1003
1004    XQueryColors(XtDisplay(w), c_map, colors, 2);
1005    XRecolorCursor(XtDisplay(w), cursor, colors, colors + 1);
1006    XDefineCursor(XtDisplay(w), XtWindow(w), cursor);
1007}
1008
1009/*	Function Name: ChangeLabel
1010 *	Description: This function changes the label field of the
1011 *                   given widget to the string in str.
1012 *	Arguments: w - the widget.
1013 *                 str - the string to change the label to.
1014 *	Returns: none
1015 */
1016
1017void
1018ChangeLabel(Widget w, const char *str)
1019{
1020    Arg arglist[3];             /* An argument list. */
1021
1022    if (w == NULL)
1023        return;
1024
1025    XtSetArg(arglist[0], XtNlabel, str);
1026
1027/* shouldn't really have to do this. */
1028    XtSetArg(arglist[1], XtNwidth, 0);
1029    XtSetArg(arglist[2], XtNheight, 0);
1030
1031    XtSetValues(w, arglist, (Cardinal) 1);
1032}
1033
1034/*
1035 * In an ideal world this would be part of the XToolkit, and I would not
1036 * have to do it, but such is life sometimes.  Perhaps in X11R3.
1037 */
1038
1039/*	Function Name: PositionCenter
1040 *	Description: This function positions the given widgets center
1041 *                   in the following location.
1042 *	Arguments: widget - the widget widget to position
1043 *                 x,y - The location for the center of the widget
1044 *                 above - number of pixels above center to locate this widget
1045 *                 left - number of pixels left of center to locate this widget
1046 *                 h_space, v_space - how close to get to the edges of the
1047 *                                    parent window.
1048 *	Returns: none
1049 *      Note:  This should only be used with a popup widget that has override
1050 *             redirect set.
1051 */
1052
1053void
1054PositionCenter(Widget widget, int x, int y, int above, int left, int v_space,
1055               int h_space)
1056{
1057    Arg wargs[2];
1058    int x_temp, y_temp;         /* location of the new window. */
1059    int parent_height, parent_width;    /* Height and width of the parent widget or
1060                                           the root window if it has no parent. */
1061
1062    x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
1063    y_temp = y - above - Height(widget) / 2 + BorderWidth(widget);
1064
1065    parent_height = HeightOfScreen(XtScreen(widget));
1066    parent_width = WidthOfScreen(XtScreen(widget));
1067
1068/*
1069 * Check to make sure that all edges are within the viewable part of the
1070 * root window, and if not then force them to be.
1071 */
1072
1073    if (x_temp < h_space)
1074        x_temp = v_space;
1075    if (y_temp < v_space)
1076        (y_temp = 2);
1077
1078    if (y_temp + Height(widget) + v_space > parent_height)
1079        y_temp = parent_height - Height(widget) - v_space;
1080
1081    if (x_temp + Width(widget) + h_space > parent_width)
1082        x_temp = parent_width - Width(widget) - h_space;
1083
1084    XtSetArg(wargs[0], XtNx, x_temp);
1085    XtSetArg(wargs[1], XtNy, y_temp);
1086    XtSetValues(widget, wargs, 2);
1087}
1088
1089/*	Function Name: ParseEntry(entry, path, sect, page)
1090 *	Description: Parses the manual pages entry filenames.
1091 *	Arguments: str - the full path name.
1092 *                 path - the path name.      RETURNED
1093 *                 sect - the section name.   RETURNED
1094 *                 page - the page name.      RETURNED
1095 *	Returns: none.
1096 */
1097
1098void
1099ParseEntry(const char *entry, char *path, char *sect, char *page)
1100{
1101    char *c, temp[BUFSIZ];
1102
1103    strcpy(temp, entry);
1104
1105    c = strrchr(temp, '/');
1106    if (c == NULL)
1107        PrintError("Failed to find / in ParseEntry.");
1108    *c++ = '\0';
1109    if (page != NULL)
1110        strcpy(page, c);
1111
1112    c = strrchr(temp, '/');
1113    if (c == NULL)
1114        PrintError("Failed to find / in ParseEntry.");
1115    *c++ = '\0';
1116#if defined(SFORMAT) && defined(SMAN)
1117    /* sgmltoroff sometimes puts an extra ./ in the path to .so entries */
1118    if (strcmp(c, ".") == 0) {
1119        c = strrchr(temp, '/');
1120        if (c == NULL)
1121            PrintError("Failed to find / in ParseEntry.");
1122        *c++ = '\0';
1123    }
1124#endif
1125#if defined(__OpenBSD__) || defined(__NetBSD__)
1126    /* Skip machine subdirectory if present */
1127    if (strcmp(c, MACHINE) == 0) {
1128        c = strrchr(temp, '/');
1129        if (c == NULL)
1130            PrintError("Failed to find / in ParseEntry.");
1131        *c++ = '\0';
1132    }
1133#endif
1134    if (sect != NULL)
1135        strcpy(sect, c);
1136
1137    if (path != NULL)
1138        strcpy(path, temp);
1139}
1140
1141/*      Function Name: GetGlobals
1142 *      Description: Gets the pseudo globals associated with the
1143 *                   manpage associated with this widget.
1144 *      Arguments: w - a widget in the manpage.
1145 *      Returns: the pseudo globals.
1146 *      Notes: initial_widget is a globals variable.
1147 *             manglobals_context is a global variable.
1148 */
1149
1150ManpageGlobals *
1151GetGlobals(Widget w)
1152{
1153    Widget temp;
1154    caddr_t data;
1155
1156    while ((temp = XtParent(w)) != initial_widget && (temp != NULL))
1157        w = temp;
1158
1159    if (temp == NULL)
1160        XtAppError(XtWidgetToApplicationContext(w),
1161                   "Xman: Could not locate widget in tree, exiting");
1162
1163    if (XFindContext(XtDisplay(w), XtWindow(w),
1164                     manglobals_context, &data) != XCSUCCESS)
1165        XtAppError(XtWidgetToApplicationContext(w),
1166                   "Xman: Could not find global data, exiting");
1167
1168    return ((ManpageGlobals *) data);
1169}
1170
1171/*      Function Name: SaveGlobals
1172 *      Description: Saves the pseudo globals on the widget passed
1173 *                   to this function, although GetGlobals assumes that
1174 *                   the data is associated with the popup child of topBox.
1175 *      Arguments: w - the widget to associate the data with.
1176 *                 globals - data to associate with this widget.
1177 *      Returns: none.
1178 *      Notes: WIDGET MUST BE REALIZED.
1179 *             manglobals_context is a global variable.
1180 */
1181
1182void
1183SaveGlobals(Widget w, ManpageGlobals * globals)
1184{
1185    if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
1186                     (caddr_t) globals) != XCSUCCESS)
1187        XtAppError(XtWidgetToApplicationContext(w),
1188                   "Xman: Could not save global data, are you out of memory?");
1189}
1190
1191/*      Function Name: RemoveGlobals
1192 *      Description: Removes the pseudo globals from the widget passed
1193 *                   to this function.
1194 *      Arguments: w - the widget to remove the data from.
1195 *      Returns: none.
1196 *      Notes: WIDGET MUST BE REALIZED.
1197 *             manglobals_context is a global variable.
1198 */
1199
1200void
1201RemoveGlobals(Widget w)
1202{
1203    if (XDeleteContext(XtDisplay(w), XtWindow(w),
1204                       manglobals_context) != XCSUCCESS)
1205        XtAppError(XtWidgetToApplicationContext(w),
1206                   "Xman: Could not remove global data?");
1207}
1208