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