misc.c revision e8f4a63f
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, omask;
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    omask = umask(077);
396    fd = mkstemp(tmp);
397    umask(omask);
398    if (fd < 0) {
399        PopupWarning(man_globals, "Error creating a temp file");
400        return FALSE;
401    }
402    *output_file = fdopen(fd, "r");
403    if (*output_file == NULL) {
404        remove(tmp);
405        close(fd);
406        PopupWarning(man_globals, "Error opening temp file");
407        return FALSE;
408    }
409    strcpy(output, tmp);
410
411#ifdef GZIP_EXTENSION
412    if (streq(filename + strlen(filename) - strlen(GZIP_EXTENSION),
413              GZIP_EXTENSION))
414        snprintf(cmdbuf, sizeof(cmdbuf), GUNZIP_FORMAT, filename, output);
415    else
416#endif
417#ifdef BZIP2_EXTENSION
418    if (streq(filename + strlen(filename) - strlen(BZIP2_EXTENSION),
419                  BZIP2_EXTENSION))
420        snprintf(cmdbuf, sizeof(cmdbuf), BUNZIP2_FORMAT, filename, output);
421    else
422#endif
423#ifdef LZMA_EXTENSION
424    if (streq(filename + strlen(filename) - strlen(LZMA_EXTENSION),
425                  LZMA_EXTENSION))
426        snprintf(cmdbuf, sizeof(cmdbuf), UNLZMA_FORMAT, filename, output);
427    else
428#endif
429        snprintf(cmdbuf, sizeof(cmdbuf), UNCOMPRESS_FORMAT, filename, output);
430    if (system(cmdbuf) == 0)    /* execute search. */
431        return (TRUE);
432
433    snprintf(error_buf, sizeof(error_buf),
434             "Error while uncompressing, command was: %s", cmdbuf);
435    PopupWarning(man_globals, error_buf);
436    return (FALSE);
437}
438
439#if defined(SMAN) && defined(SFORMAT)
440/*	Function Name: SgmlToRoffNamed
441 *	Description: This function will attempt to find an SGML man
442 *                   page and convert it to roff format.
443 *	Arguments: man_globals - the pseudo global info.
444 *                 filename - name of file to uncompress.
445 * RETURNED        output - the file name output (must be an allocated string).
446 *	Returns:; TRUE if the file was found.
447 */
448
449static Boolean
450SgmlToRoffNamed(ManpageGlobals * man_globals, char *filename, char *output,
451                FILE ** output_file)
452{
453    char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
454    struct stat junk;
455    int fd, omask;
456
457    if (stat(filename, &junk) != 0) {   /* Check for existence of the file. */
458        if (errno != ENOENT) {
459            snprintf(error_buf, sizeof(error_buf),
460                     "Error while stating file %s, errno = %d", filename,
461                     errno);
462            PopupWarning(man_globals, error_buf);
463        }
464        return (FALSE);
465    }
466
467    strcpy(tmp, MANTEMP);       /* get a temp file. */
468    omask = umask(077):
469    fd = mkstemp(tmp);
470    umask(omask);
471    if (fd < 0) {
472        PopupWarning(man_globals, "Error creating a temp file");
473        return FALSE;
474    }
475    *output_file = fdopen(fd, "r");
476    if (*output_file == NULL) {
477        remove(tmp);
478        close(fd);
479        PopupWarning(man_globals, "Error opening temp file");
480        return FALSE;
481    }
482    strcpy(output, tmp);
483
484    snprintf(cmdbuf, sizeof(cmdbuf), "%s %s >> %s", SFORMAT, filename, output);
485    if (system(cmdbuf) == 0)    /* execute search. */
486        return (TRUE);
487
488    snprintf(error_buf, sizeof(error_buf),
489             "Error while converting from sgml, command was: %s", cmdbuf);
490    PopupWarning(man_globals, error_buf);
491    return (FALSE);
492}
493#endif                          /* defined (SMAN) && defined(SFORMAT) */
494
495/*	Function Name: Format
496 *	Description: This function formats the manual pages and interfaces
497 *                   with the user.
498 *	Arguments: man_globals - the pseudo globals
499 *                 file - the file pointer to use and return
500 *                 entry - the current entry struct.
501 *                 current_box - The current directory being displayed.
502 *	Returns: none.
503 */
504
505/* ARGSUSED */
506FILE *
507Format(ManpageGlobals * man_globals, const char *entry)
508{
509    FILE *file = NULL;
510    int fd, omask;
511
512    Widget manpage = man_globals->manpagewidgets.manpage;
513    char cmdbuf[BUFSIZ], tmp[BUFSIZ], filename[BUFSIZ], error_buf[BUFSIZ];
514    char path[BUFSIZ], sect[BUFSIZ];
515    XEvent event;
516    Position x, y;              /* location to pop up the
517                                   "would you like to save" widget. */
518
519    if (!UncompressUnformatted(man_globals, entry, filename, &file)) {
520        /* We Really could not find it, this should never happen, yea right. */
521        snprintf(error_buf, sizeof(error_buf),
522                 "Could not open manual page, %s", entry);
523        PopupWarning(man_globals, error_buf);
524        XtPopdown(XtParent(man_globals->standby));
525        return (NULL);
526    }
527
528    if (file != NULL) {
529        char line[BUFSIZ];
530
531        if (fgets(line, sizeof(line), file) != NULL) {
532            if (strncmp(line, ".so ", 4) == 0) {
533                size_t len = strlen(line); /* must be >= 4 to pass strncmp */
534                if (line[len - 1] == '\n')
535                    line[len - 1] = '\0';
536                fclose(file);
537                remove(filename);
538                if (line[4] != '/') {
539                    char *ptr = NULL;
540
541                    strcpy(tmp, entry);
542                    if ((ptr = strrchr(tmp, '/')) != NULL) {
543                        *ptr = '\0';
544                        if ((ptr = strrchr(tmp, '/')) != NULL)
545                            ptr[1] = '\0';
546                    }
547                }
548                else
549                    *tmp = '\0';
550                snprintf(filename, sizeof(filename), "%s%s", tmp, line + 4);
551
552                return (Format(man_globals, filename));
553            }
554        }
555        fclose(file);
556    }
557
558    Popup(XtParent(man_globals->standby), XtGrabExclusive);
559    while (!XCheckTypedWindowEvent(XtDisplay(man_globals->standby),
560                                   XtWindow(man_globals->standby),
561                                   Expose, &event));
562    XtDispatchEvent(&event);
563    XFlush(XtDisplay(man_globals->standby));
564
565    strcpy(tmp, MANTEMP);       /* Get a temp file. */
566    omask = umask(077);
567    fd = mkstemp(tmp);
568    umask(omask);
569    if (fd >= 0) {
570        file = fdopen(fd, "r");
571        if (file == NULL) {
572            remove(tmp);
573            close(fd);
574        }
575    }
576    else
577        file = NULL;
578    if (file == NULL) {
579        PopupWarning(man_globals, "Something went wrong in opening the "
580                     "temp file, try cleaning up /tmp");
581        return NULL;
582    }
583    strcpy(man_globals->tempfile, tmp);
584
585    ParseEntry(entry, path, sect, NULL);
586
587#ifndef HANDLE_ROFFSEQ
588    snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s >> %s %s", path, TBL,
589             filename, FORMAT, man_globals->tempfile, "2> /dev/null");
590#else
591    /* Handle more flexible way of specifying the formatting pipeline */
592    if (!ConstructCommand(cmdbuf, path, filename, man_globals->tempfile)) {
593        PopupWarning(man_globals, "Constructed command was too long!");
594        fclose(file);
595        file = NULL;
596    }
597    else
598#endif                          /* HANDLE_ROFFSEQ */
599
600    if (system(cmdbuf) != 0) {  /* execute search. */
601        snprintf(error_buf, sizeof(error_buf),
602                 "Something went wrong trying to run the command: %s", cmdbuf);
603        PopupWarning(man_globals, error_buf);
604        fclose(file);
605        file = NULL;
606    }
607    else {
608        if (file != NULL) {
609            XtPopdown(XtParent(man_globals->standby));
610
611            if ((man_globals->save == NULL) ||
612                (man_globals->manpagewidgets.manpage == NULL))
613                remove(man_globals->tempfile);
614            else {
615                char *ptr, catdir[BUFSIZ];
616
617                /*
618                 * If the catdir is writable then ask the user if he/she wants to
619                 * write the man page to it.
620                 */
621
622                strcpy(catdir, man_globals->save_file);
623                if ((ptr = strrchr(catdir, '/')) != NULL) {
624                    *ptr = '\0';
625
626                    if (access(catdir, W_OK) != 0)
627                        remove(man_globals->tempfile);
628                    else {
629                        x = (Position) Width(man_globals->manpagewidgets.
630                                             manpage) / 2;
631                        y = (Position) Height(man_globals->manpagewidgets.
632                                              manpage) / 2;
633                        XtTranslateCoords(manpage, x, y, &x, &y);
634                        PositionCenter(man_globals->save, (int) x, (int) y, 0,
635                                       0, 0, 0);
636                        XtPopup(man_globals->save, XtGrabExclusive);
637                    }
638                }
639                else
640                    remove(man_globals->tempfile);
641            }
642        }
643    }
644
645    /*
646     * If the original was compressed or in another format, delete temporary file.
647     */
648    if (man_globals->deletetempfile)
649        remove(filename);
650
651    return (file);
652}
653
654#ifdef HANDLE_ROFFSEQ
655/*      Function Name: ConstructCommand
656 *      Description: Constructs the pipeline of commands necessary to format
657 *                   a manual page.
658 *      Arguments: cmdbuf - the buffer into which to write the command
659 *                 path - the directory in which the original man page resides
660 *                 filename - the (uncompressed) manpage source file
661 *                 tempfile - the name of a temporary file to direct the final
662 *                  output of the pipeline into
663 *      Returns: TRUE if the command fit into the buffer, FALSE if it would
664 *               be too long (more than BUFSIZ characters)
665 */
666static Boolean
667ConstructCommand(char *cmdbuf, const char *path,
668                 const char *filename, const char *tempfile)
669{
670#ifdef HAVE_MANDB
671    int used = snprintf(cmdbuf, BUFSIZ, "man -l %s > %s 2>/dev/null",
672                        filename, tempfile);
673    if (used >= BUFSIZ - 1)
674	return FALSE;
675    return TRUE;
676#else
677    /* The original code did the following to produce a command line:
678     *   sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
679     *      filename, FORMAT, man_globals->tempfile, "2> /dev/null");
680     * We are more flexible and follow more or less the algorithm used
681     * by the Linux man command:
682     *  + Obtain a string of letters from the following sources in order
683     *    of preference:
684     *    + a command line option (not implemented in xman; it's probably not
685     *      useful)
686     *    + the first line of the manpage source, if it is of the form:
687     *      '\" <string>
688     *    + the MANROFFSEQ environment variable
689     *    + a default string; this is "".
690     *  + Interpret the string as a pipeline of filters:
691     *    + e = eqn   g = grap   p = pic   t = tbl   v = vgrind   r = refer
692     *  + zsoelim is always run as the first preprocessor in any case.
693     *
694     * Strictly speaking we should save a catpage iff the string comes
695     * from the file or is the default.
696     *
697     * You'll notice that we format a man page into ASCII text output and then
698     * attempt to interpret things like L^HL as bold and so forth. This
699     * is so obviously the Wrong Thing it's untrue.
700     */
701    char *c = cmdbuf;           /* current posn in buffer */
702    int left = BUFSIZ;          /* space left in buffer */
703    int used;
704    const char *fmt;
705    char fmtbuf[128];
706
707    fmt = NULL;
708    /* If you have a command line option that gives a setting for fmt,
709       set it here. */
710
711    if (!fmt) {
712        /* This is the tricky bit: extract a format string from the source file
713         * Annoyingly, filename might be relative or absolute. We cheat and
714         * use system to get the thing to a known absolute filename.
715         */
716        FILE *file;
717        int gotfmt = 0;    /* set to 1 if we got a directive from source */
718        char fname[PATH_MAX];
719
720        if (filename[0] == '/') {
721            snprintf(fname, sizeof(fname), "%s", filename);
722        }
723        else {
724            snprintf(fname, sizeof(fname), "%s/%s", path, filename);
725        }
726        if ((file = fopen(fname, "r")) != NULL) {
727            if ((fgets(fmtbuf, sizeof(fmtbuf), file)) &&
728                (!memcmp(fmtbuf, "'\\\" ", 4))) {
729                /* that's squote-backslash-dquote-space */
730                int len = strlen(fmtbuf);
731
732                if (len && (fmtbuf[len - 1] == '\n')) {
733                    fmtbuf[len - 1] = 0;
734                    fmt = fmtbuf + 3;
735                    gotfmt++;
736                }
737            }
738            fclose(file);
739        }
740        if (!gotfmt) {          /* not there or some error */
741            fmt = getenv("MANROFFSEQ");
742        }
743    }
744
745    if (!fmt) {
746        fmt = DEFAULT_MANROFFSEQ;
747    }
748
749    /* Start with the first fixed part of the command line */
750    used = snprintf(c, left, "cd %s; %s %s ", path, ZSOELIM, filename);
751    left -= used;
752    c += used;
753    if (left <= 1)
754        return (FALSE);
755
756    /* Now add preprocessors of the form '| processor' */
757    for (; *fmt; fmt++) {
758        const char *filter;
759
760        switch (*fmt) {
761        case 'e':
762            filter = EQN;
763            break;
764        case 'g':
765            filter = GRAP;
766            break;
767        case 'p':
768            filter = ROFF_PIC;
769            break;
770        case 't':
771            filter = TBL;
772            break;
773        case 'v':
774            filter = VGRIND;
775            break;
776        case 'r':
777            filter = REFER;
778            break;
779        default:
780            filter = NULL;
781            break;
782        }
783        if (filter) {
784            used = snprintf(c, left, " | %s ", filter);
785            left -= used;
786            c += used;
787            if (left <= 1)
788                return (FALSE);
789        }
790    }
791
792    /* Now add the fixed trailing part 'formatprog > tempfile 2> /dev/null' */
793    used = snprintf(c, left, " | %s >> %s 2>/dev/null", FORMAT, tempfile);
794    left -= used;
795    if (left <= 1)
796        return (FALSE);
797
798    return (TRUE);
799#endif /* man-db */
800}
801#endif                          /* HANDLE_ROFFSEQ */
802
803/*	Function Name: UncompressUnformatted
804 *	Description: Finds an uncompressed unformatted manual page.
805 *	Arguments: man_globals - the pseudo global structure.
806 *                 entry - the manual page entry.
807 * RETURNED        filename - location to put the name of the file.
808 *	Returns: TRUE if the file was found.
809 */
810
811static Boolean
812UncompressUnformatted(ManpageGlobals * man_globals, const char *entry,
813                      char *filename, FILE ** file)
814{
815    char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
816    int len_cat = strlen(CAT), len_man = strlen(MAN);
817
818#if defined(SMAN) && defined(SFORMAT)
819    int len_sman = strlen(SMAN);
820#endif
821
822    ParseEntry(entry, path, section, page);
823
824    man_globals->bzip2 = FALSE;
825    man_globals->lzma = FALSE;
826
827#if defined(__OpenBSD__) || defined(__NetBSD__)
828    /*
829     * look for uncompressed file in machine subdir first
830     */
831    snprintf(filename, BUFSIZ, "%s/%s%s/%s/%s", path, MAN,
832             section + len_cat, MACHINE, page);
833    if (access(filename, R_OK) == 0) {
834        man_globals->compress = FALSE;
835        man_globals->gzip = FALSE;
836        man_globals->deletetempfile = FALSE;
837        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
838                 "%s/%s%s/%s/%s", path, CAT, section + len_cat, MACHINE, page);
839        return (TRUE);
840    }
841    /*
842     * Then for compressed files in an uncompressed directory.
843     */
844    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
845    if (UncompressNamed(man_globals, input, filename, file)) {
846        man_globals->compress = TRUE;
847        man_globals->deletetempfile = TRUE;
848        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
849                 "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
850                 COMPRESSION_EXTENSION);
851        return (TRUE);
852    }
853#ifdef GZIP_EXTENSION
854    else {
855        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
856        if (UncompressNamed(man_globals, input, filename, file)) {
857            man_globals->compress = TRUE;
858            man_globals->gzip = TRUE;
859            man_globals->deletetempfile = TRUE;
860            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
861                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
862                     GZIP_EXTENSION);
863            return (TRUE);
864        }
865    }
866#endif                          /* GZIP_EXTENSION */
867#endif                          /* __OpenBSD__ || __NetBSD__ */
868
869#ifdef BZIP2_EXTENSION
870    {
871        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
872        if (UncompressNamed(man_globals, input, filename, file)) {
873            man_globals->compress = TRUE;
874            man_globals->gzip = FALSE;
875            man_globals->bzip2 = TRUE;
876            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
877                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
878                     BZIP2_EXTENSION);
879            return (TRUE);
880        }
881    }
882#endif                          /* BZIP2_EXTENSION */
883
884#ifdef LZMA_EXTENSION
885    {
886        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
887        if (UncompressNamed(man_globals, input, filename, file)) {
888            man_globals->compress = TRUE;
889            man_globals->gzip = FALSE;
890            man_globals->lzma = TRUE;
891            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
892                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
893                     LZMA_EXTENSION);
894            return (TRUE);
895        }
896    }
897#endif                          /* LZMA_EXTENSION */
898
899/*
900 * Look for uncompressed file first.
901 */
902
903    snprintf(filename, BUFSIZ, "%s/%s%s/%s", path, MAN, section + len_man,
904             page);
905    if (access(filename, R_OK) == 0) {
906        man_globals->compress = FALSE;
907        man_globals->gzip = FALSE;
908        man_globals->deletetempfile = FALSE;
909        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
910                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
911        return (TRUE);
912    }
913
914#if defined(SMAN) && defined(SFORMAT)
915    /*
916     * Look for uncompressed sgml file next.
917     */
918
919    snprintf(input, BUFSIZ, "%s/%s%s/%s", path, SMAN, section + len_sman, page);
920    if (SgmlToRoffNamed(man_globals, input, filename, file)) {
921        man_globals->compress = FALSE;
922        man_globals->gzip = FALSE;
923        man_globals->deletetempfile = TRUE;
924        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
925                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
926        return (TRUE);
927    }
928#endif
929
930/*
931 * Then for compressed files in an uncompressed directory.
932 */
933
934    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
935    if (UncompressNamed(man_globals, input, filename, file)) {
936        man_globals->compress = TRUE;
937        man_globals->deletetempfile = 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                 COMPRESSION_EXTENSION);
941        return (TRUE);
942    }
943#ifdef GZIP_EXTENSION
944    else {
945        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
946        if (UncompressNamed(man_globals, input, filename, file)) {
947            man_globals->compress = TRUE;
948            man_globals->gzip = TRUE;
949            man_globals->deletetempfile = 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                     GZIP_EXTENSION);
953            return (TRUE);
954        }
955    }
956#endif
957
958#ifdef BZIP2_EXTENSION
959    {
960        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
961        if (UncompressNamed(man_globals, input, filename, file)) {
962            man_globals->compress = TRUE;
963            man_globals->gzip = 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                     BZIP2_EXTENSION);
967            return (TRUE);
968        }
969    }
970#endif
971
972#ifdef LZMA_EXTENSION
973    {
974        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
975        if (UncompressNamed(man_globals, input, filename, file)) {
976            man_globals->compress = TRUE;
977            man_globals->lzma = TRUE;
978            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
979                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
980                     LZMA_EXTENSION);
981            return (TRUE);
982        }
983    }
984#endif
985
986/*
987 * And lastly files in a compressed directory.
988 */
989
990    snprintf(input, sizeof(input), "%s/%s%s.%s/%s", path,
991             MAN, section + len_man, COMPRESSION_EXTENSION, page);
992    if (UncompressNamed(man_globals, input, filename, file)) {
993        man_globals->compress = TRUE;
994        man_globals->deletetempfile = TRUE;
995        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
996                 "%s/%s%s.%s/%s", path, CAT, section + len_cat,
997                 COMPRESSION_EXTENSION, page);
998        return (TRUE);
999    }
1000    return (FALSE);
1001}
1002
1003/*	Function Name: AddCursor
1004 *	Description: This function adds the cursor to the window.
1005 *	Arguments: w - the widget to add the cursor to.
1006 *                 cursor - the cursor to add to this widget.
1007 *	Returns: none
1008 */
1009
1010void
1011AddCursor(Widget w, Cursor cursor)
1012{
1013    XColor colors[2];
1014    Arg args[10];
1015    Cardinal num_args = 0;
1016    Colormap c_map;
1017
1018    if (!XtIsRealized(w)) {
1019        PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
1020        return;
1021    }
1022
1023    XtSetArg(args[num_args], XtNcolormap, &c_map);
1024    num_args++;
1025    XtGetValues(w, args, num_args);
1026
1027    colors[0].pixel = resources.cursors.fg_color;
1028    colors[1].pixel = resources.cursors.bg_color;
1029
1030    XQueryColors(XtDisplay(w), c_map, colors, 2);
1031    XRecolorCursor(XtDisplay(w), cursor, colors, colors + 1);
1032    XDefineCursor(XtDisplay(w), XtWindow(w), cursor);
1033}
1034
1035/*	Function Name: ChangeLabel
1036 *	Description: This function changes the label field of the
1037 *                   given widget to the string in str.
1038 *	Arguments: w - the widget.
1039 *                 str - the string to change the label to.
1040 *	Returns: none
1041 */
1042
1043void
1044ChangeLabel(Widget w, const char *str)
1045{
1046    Arg arglist[3];             /* An argument list. */
1047
1048    if (w == NULL)
1049        return;
1050
1051    XtSetArg(arglist[0], XtNlabel, str);
1052
1053/* shouldn't really have to do this. */
1054    XtSetArg(arglist[1], XtNwidth, 0);
1055    XtSetArg(arglist[2], XtNheight, 0);
1056
1057    XtSetValues(w, arglist, (Cardinal) 1);
1058}
1059
1060/*
1061 * In an ideal world this would be part of the XToolkit, and I would not
1062 * have to do it, but such is life sometimes.  Perhaps in X11R3.
1063 */
1064
1065/*	Function Name: PositionCenter
1066 *	Description: This function positions the given widgets center
1067 *                   in the following location.
1068 *	Arguments: widget - the widget widget to position
1069 *                 x,y - The location for the center of the widget
1070 *                 above - number of pixels above center to locate this widget
1071 *                 left - number of pixels left of center to locate this widget
1072 *                 h_space, v_space - how close to get to the edges of the
1073 *                                    parent window.
1074 *	Returns: none
1075 *      Note:  This should only be used with a popup widget that has override
1076 *             redirect set.
1077 */
1078
1079void
1080PositionCenter(Widget widget, int x, int y, int above, int left, int v_space,
1081               int h_space)
1082{
1083    Arg wargs[2];
1084    int x_temp, y_temp;         /* location of the new window. */
1085    int parent_height, parent_width;    /* Height and width of the parent widget or
1086                                           the root window if it has no parent. */
1087
1088    x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
1089    y_temp = y - above - Height(widget) / 2 + BorderWidth(widget);
1090
1091    parent_height = HeightOfScreen(XtScreen(widget));
1092    parent_width = WidthOfScreen(XtScreen(widget));
1093
1094/*
1095 * Check to make sure that all edges are within the viewable part of the
1096 * root window, and if not then force them to be.
1097 */
1098
1099    if (x_temp < h_space)
1100        x_temp = v_space;
1101    if (y_temp < v_space)
1102        (y_temp = 2);
1103
1104    if (y_temp + Height(widget) + v_space > parent_height)
1105        y_temp = parent_height - Height(widget) - v_space;
1106
1107    if (x_temp + Width(widget) + h_space > parent_width)
1108        x_temp = parent_width - Width(widget) - h_space;
1109
1110    XtSetArg(wargs[0], XtNx, x_temp);
1111    XtSetArg(wargs[1], XtNy, y_temp);
1112    XtSetValues(widget, wargs, 2);
1113}
1114
1115/*	Function Name: ParseEntry(entry, path, sect, page)
1116 *	Description: Parses the manual pages entry filenames.
1117 *	Arguments: str - the full path name.
1118 *                 path - the path name.      RETURNED
1119 *                 sect - the section name.   RETURNED
1120 *                 page - the page name.      RETURNED
1121 *	Returns: none.
1122 */
1123
1124void
1125ParseEntry(const char *entry, char *path, char *sect, char *page)
1126{
1127    char *c, temp[BUFSIZ];
1128
1129    strcpy(temp, entry);
1130
1131    c = strrchr(temp, '/');
1132    if (c == NULL)
1133        PrintError("Failed to find / in ParseEntry.");
1134    *c++ = '\0';
1135    if (page != NULL)
1136        strcpy(page, c);
1137
1138    c = strrchr(temp, '/');
1139    if (c == NULL)
1140        PrintError("Failed to find / in ParseEntry.");
1141    *c++ = '\0';
1142#if defined(SFORMAT) && defined(SMAN)
1143    /* sgmltoroff sometimes puts an extra ./ in the path to .so entries */
1144    if (strcmp(c, ".") == 0) {
1145        c = strrchr(temp, '/');
1146        if (c == NULL)
1147            PrintError("Failed to find / in ParseEntry.");
1148        *c++ = '\0';
1149    }
1150#endif
1151#if defined(__OpenBSD__) || defined(__NetBSD__)
1152    /* Skip machine subdirectory if present */
1153    if (strcmp(c, MACHINE) == 0) {
1154        c = strrchr(temp, '/');
1155        if (c == NULL)
1156            PrintError("Failed to find / in ParseEntry.");
1157        *c++ = '\0';
1158    }
1159#endif
1160    if (sect != NULL)
1161        strcpy(sect, c);
1162
1163    if (path != NULL)
1164        strcpy(path, temp);
1165}
1166
1167/*      Function Name: GetGlobals
1168 *      Description: Gets the pseudo globals associated with the
1169 *                   manpage associated with this widget.
1170 *      Arguments: w - a widget in the manpage.
1171 *      Returns: the pseudo globals.
1172 *      Notes: initial_widget is a globals variable.
1173 *             manglobals_context is a global variable.
1174 */
1175
1176ManpageGlobals *
1177GetGlobals(Widget w)
1178{
1179    Widget temp;
1180    caddr_t data;
1181
1182    while ((temp = XtParent(w)) != initial_widget && (temp != NULL))
1183        w = temp;
1184
1185    if (temp == NULL)
1186        XtAppError(XtWidgetToApplicationContext(w),
1187                   "Xman: Could not locate widget in tree, exiting");
1188
1189    if (XFindContext(XtDisplay(w), XtWindow(w),
1190                     manglobals_context, &data) != XCSUCCESS)
1191        XtAppError(XtWidgetToApplicationContext(w),
1192                   "Xman: Could not find global data, exiting");
1193
1194    return ((ManpageGlobals *) data);
1195}
1196
1197/*      Function Name: SaveGlobals
1198 *      Description: Saves the pseudo globals on the widget passed
1199 *                   to this function, although GetGlobals assumes that
1200 *                   the data is associated with the popup child of topBox.
1201 *      Arguments: w - the widget to associate the data with.
1202 *                 globals - data to associate with this widget.
1203 *      Returns: none.
1204 *      Notes: WIDGET MUST BE REALIZED.
1205 *             manglobals_context is a global variable.
1206 */
1207
1208void
1209SaveGlobals(Widget w, ManpageGlobals * globals)
1210{
1211    if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
1212                     (caddr_t) globals) != XCSUCCESS)
1213        XtAppError(XtWidgetToApplicationContext(w),
1214                   "Xman: Could not save global data, are you out of memory?");
1215}
1216
1217/*      Function Name: RemoveGlobals
1218 *      Description: Removes the pseudo globals from the widget passed
1219 *                   to this function.
1220 *      Arguments: w - the widget to remove the data from.
1221 *      Returns: none.
1222 *      Notes: WIDGET MUST BE REALIZED.
1223 *             manglobals_context is a global variable.
1224 */
1225
1226void
1227RemoveGlobals(Widget w)
1228{
1229    if (XDeleteContext(XtDisplay(w), XtWindow(w),
1230                       manglobals_context) != XCSUCCESS)
1231        XtAppError(XtWidgetToApplicationContext(w),
1232                   "Xman: Could not remove global data?");
1233}
1234