misc.c revision 6a088f60
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    /* The original code did the following to produce a command line:
671     *   sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
672     *      filename, FORMAT, man_globals->tempfile, "2> /dev/null");
673     * We are more flexible and follow more or less the algorithm used
674     * by the Linux man command:
675     *  + Obtain a string of letters from the following sources in order
676     *    of preference:
677     *    + a command line option (not implemented in xman; it's probably not
678     *      useful)
679     *    + the first line of the manpage source, if it is of the form:
680     *      '\" <string>
681     *    + the MANROFFSEQ environment variable
682     *    + a default string; this is "".
683     *  + Interpret the string as a pipeline of filters:
684     *    + e = eqn   g = grap   p = pic   t = tbl   v = vgrind   r = refer
685     *  + zsoelim is always run as the first preprocessor in any case.
686     *
687     * Strictly speaking we should save a catpage iff the string comes
688     * from the file or is the default.
689     *
690     * You'll notice that we format a man page into ASCII text output and then
691     * attempt to interpret things like L^HL as bold and so forth. This
692     * is so obviously the Wrong Thing it's untrue.
693     */
694    char *c = cmdbuf;           /* current posn in buffer */
695    int left = BUFSIZ;          /* space left in buffer */
696    int used;
697    const char *fmt;
698    char fmtbuf[128];
699
700    fmt = NULL;
701    /* If you have a command line option that gives a setting for fmt,
702       set it here. */
703
704    if (!fmt) {
705        /* This is the tricky bit: extract a format string from the source file
706         * Annoyingly, filename might be relative or absolute. We cheat and
707         * use system to get the thing to a known absolute filename.
708         */
709        FILE *file;
710        int gotfmt = 0;    /* set to 1 if we got a directive from source */
711        char fname[PATH_MAX];
712
713        if (filename[0] == '/') {
714            snprintf(fname, sizeof(fname), "%s", filename);
715        }
716        else {
717            snprintf(fname, sizeof(fname), "%s/%s", path, filename);
718        }
719        if ((file = fopen(fname, "r")) != NULL) {
720            if ((fgets(fmtbuf, sizeof(fmtbuf), file)) &&
721                (!memcmp(fmtbuf, "'\\\" ", 4))) {
722                /* that's squote-backslash-dquote-space */
723                int len = strlen(fmtbuf);
724
725                if (len && (fmtbuf[len - 1] == '\n')) {
726                    fmtbuf[len - 1] = 0;
727                    fmt = fmtbuf + 3;
728                    gotfmt++;
729                }
730            }
731            fclose(file);
732        }
733        if (!gotfmt) {          /* not there or some error */
734            fmt = getenv("MANROFFSEQ");
735        }
736    }
737
738    if (!fmt) {
739        fmt = DEFAULT_MANROFFSEQ;
740    }
741
742    /* Start with the first fixed part of the command line */
743    used = snprintf(c, left, "cd %s; %s %s ", path, ZSOELIM, filename);
744    left -= used;
745    c += used;
746    if (left <= 1)
747        return (FALSE);
748
749    /* Now add preprocessors of the form '| processor' */
750    for (; *fmt; fmt++) {
751        const char *filter;
752
753        switch (*fmt) {
754        case 'e':
755            filter = EQN;
756            break;
757        case 'g':
758            filter = GRAP;
759            break;
760        case 'p':
761            filter = ROFF_PIC;
762            break;
763        case 't':
764            filter = TBL;
765            break;
766        case 'v':
767            filter = VGRIND;
768            break;
769        case 'r':
770            filter = REFER;
771            break;
772        default:
773            filter = NULL;
774            break;
775        }
776        if (filter) {
777            used = snprintf(c, left, " | %s ", filter);
778            left -= used;
779            c += used;
780            if (left <= 1)
781                return (FALSE);
782        }
783    }
784
785    /* Now add the fixed trailing part 'formatprog > tempfile 2> /dev/null' */
786    used = snprintf(c, left, " | %s >> %s 2>/dev/null", FORMAT, tempfile);
787    left -= used;
788    if (left <= 1)
789        return (FALSE);
790
791    return (TRUE);
792}
793#endif                          /* HANDLE_ROFFSEQ */
794
795/*	Function Name: UncompressUnformatted
796 *	Description: Finds an uncompressed unformatted manual page.
797 *	Arguments: man_globals - the pseudo global structure.
798 *                 entry - the manual page entry.
799 * RETURNED        filename - location to put the name of the file.
800 *	Returns: TRUE if the file was found.
801 */
802
803static Boolean
804UncompressUnformatted(ManpageGlobals * man_globals, const char *entry,
805                      char *filename, FILE ** file)
806{
807    char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
808    int len_cat = strlen(CAT), len_man = strlen(MAN);
809
810#if defined(SMAN) && defined(SFORMAT)
811    int len_sman = strlen(SMAN);
812#endif
813
814    ParseEntry(entry, path, section, page);
815
816    man_globals->bzip2 = FALSE;
817    man_globals->lzma = FALSE;
818
819#if defined(__OpenBSD__) || defined(__NetBSD__)
820    /*
821     * look for uncompressed file in machine subdir first
822     */
823    snprintf(filename, BUFSIZ, "%s/%s%s/%s/%s", path, MAN,
824             section + len_cat, MACHINE, page);
825    if (access(filename, R_OK) == 0) {
826        man_globals->compress = FALSE;
827        man_globals->gzip = FALSE;
828        man_globals->deletetempfile = FALSE;
829        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
830                 "%s/%s%s/%s/%s", path, CAT, section + len_cat, MACHINE, page);
831        return (TRUE);
832    }
833    /*
834     * Then for compressed files in an uncompressed directory.
835     */
836    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
837    if (UncompressNamed(man_globals, input, filename, file)) {
838        man_globals->compress = TRUE;
839        man_globals->deletetempfile = TRUE;
840        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
841                 "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
842                 COMPRESSION_EXTENSION);
843        return (TRUE);
844    }
845#ifdef GZIP_EXTENSION
846    else {
847        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
848        if (UncompressNamed(man_globals, input, filename, file)) {
849            man_globals->compress = TRUE;
850            man_globals->gzip = TRUE;
851            man_globals->deletetempfile = TRUE;
852            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
853                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
854                     GZIP_EXTENSION);
855            return (TRUE);
856        }
857    }
858#endif                          /* GZIP_EXTENSION */
859#endif                          /* __OpenBSD__ || __NetBSD__ */
860
861#ifdef BZIP2_EXTENSION
862    {
863        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
864        if (UncompressNamed(man_globals, input, filename, file)) {
865            man_globals->compress = TRUE;
866            man_globals->gzip = FALSE;
867            man_globals->bzip2 = TRUE;
868            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
869                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
870                     BZIP2_EXTENSION);
871            return (TRUE);
872        }
873    }
874#endif                          /* BZIP2_EXTENSION */
875
876#ifdef LZMA_EXTENSION
877    {
878        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
879        if (UncompressNamed(man_globals, input, filename, file)) {
880            man_globals->compress = TRUE;
881            man_globals->gzip = FALSE;
882            man_globals->lzma = TRUE;
883            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
884                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
885                     LZMA_EXTENSION);
886            return (TRUE);
887        }
888    }
889#endif                          /* LZMA_EXTENSION */
890
891/*
892 * Look for uncompressed file first.
893 */
894
895    snprintf(filename, BUFSIZ, "%s/%s%s/%s", path, MAN, section + len_man,
896             page);
897    if (access(filename, R_OK) == 0) {
898        man_globals->compress = FALSE;
899        man_globals->gzip = FALSE;
900        man_globals->deletetempfile = FALSE;
901        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
902                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
903        return (TRUE);
904    }
905
906#if defined(SMAN) && defined(SFORMAT)
907    /*
908     * Look for uncompressed sgml file next.
909     */
910
911    snprintf(input, BUFSIZ, "%s/%s%s/%s", path, SMAN, section + len_sman, page);
912    if (SgmlToRoffNamed(man_globals, input, filename, file)) {
913        man_globals->compress = FALSE;
914        man_globals->gzip = FALSE;
915        man_globals->deletetempfile = TRUE;
916        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
917                 "%s/%s%s/%s", path, CAT, section + len_cat, page);
918        return (TRUE);
919    }
920#endif
921
922/*
923 * Then for compressed files in an uncompressed directory.
924 */
925
926    snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
927    if (UncompressNamed(man_globals, input, filename, file)) {
928        man_globals->compress = TRUE;
929        man_globals->deletetempfile = TRUE;
930        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
931                 "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
932                 COMPRESSION_EXTENSION);
933        return (TRUE);
934    }
935#ifdef GZIP_EXTENSION
936    else {
937        snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
938        if (UncompressNamed(man_globals, input, filename, file)) {
939            man_globals->compress = TRUE;
940            man_globals->gzip = TRUE;
941            man_globals->deletetempfile = TRUE;
942            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
943                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
944                     GZIP_EXTENSION);
945            return (TRUE);
946        }
947    }
948#endif
949
950#ifdef BZIP2_EXTENSION
951    {
952        snprintf(input, sizeof(input), "%s.%s", filename, BZIP2_EXTENSION);
953        if (UncompressNamed(man_globals, input, filename, file)) {
954            man_globals->compress = TRUE;
955            man_globals->gzip = TRUE;
956            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
957                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
958                     BZIP2_EXTENSION);
959            return (TRUE);
960        }
961    }
962#endif
963
964#ifdef LZMA_EXTENSION
965    {
966        snprintf(input, sizeof(input), "%s.%s", filename, LZMA_EXTENSION);
967        if (UncompressNamed(man_globals, input, filename, file)) {
968            man_globals->compress = TRUE;
969            man_globals->lzma = TRUE;
970            snprintf(man_globals->save_file, sizeof(man_globals->save_file),
971                     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
972                     LZMA_EXTENSION);
973            return (TRUE);
974        }
975    }
976#endif
977
978/*
979 * And lastly files in a compressed directory.
980 */
981
982    snprintf(input, sizeof(input), "%s/%s%s.%s/%s", path,
983             MAN, section + len_man, COMPRESSION_EXTENSION, page);
984    if (UncompressNamed(man_globals, input, filename, file)) {
985        man_globals->compress = TRUE;
986        man_globals->deletetempfile = TRUE;
987        snprintf(man_globals->save_file, sizeof(man_globals->save_file),
988                 "%s/%s%s.%s/%s", path, CAT, section + len_cat,
989                 COMPRESSION_EXTENSION, page);
990        return (TRUE);
991    }
992    return (FALSE);
993}
994
995/*	Function Name: AddCursor
996 *	Description: This function adds the cursor to the window.
997 *	Arguments: w - the widget to add the cursor to.
998 *                 cursor - the cursor to add to this widget.
999 *	Returns: none
1000 */
1001
1002void
1003AddCursor(Widget w, Cursor cursor)
1004{
1005    XColor colors[2];
1006    Arg args[10];
1007    Cardinal num_args = 0;
1008    Colormap c_map;
1009
1010    if (!XtIsRealized(w)) {
1011        PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
1012        return;
1013    }
1014
1015    XtSetArg(args[num_args], XtNcolormap, &c_map);
1016    num_args++;
1017    XtGetValues(w, args, num_args);
1018
1019    colors[0].pixel = resources.cursors.fg_color;
1020    colors[1].pixel = resources.cursors.bg_color;
1021
1022    XQueryColors(XtDisplay(w), c_map, colors, 2);
1023    XRecolorCursor(XtDisplay(w), cursor, colors, colors + 1);
1024    XDefineCursor(XtDisplay(w), XtWindow(w), cursor);
1025}
1026
1027/*	Function Name: ChangeLabel
1028 *	Description: This function changes the label field of the
1029 *                   given widget to the string in str.
1030 *	Arguments: w - the widget.
1031 *                 str - the string to change the label to.
1032 *	Returns: none
1033 */
1034
1035void
1036ChangeLabel(Widget w, const char *str)
1037{
1038    Arg arglist[3];             /* An argument list. */
1039
1040    if (w == NULL)
1041        return;
1042
1043    XtSetArg(arglist[0], XtNlabel, str);
1044
1045/* shouldn't really have to do this. */
1046    XtSetArg(arglist[1], XtNwidth, 0);
1047    XtSetArg(arglist[2], XtNheight, 0);
1048
1049    XtSetValues(w, arglist, (Cardinal) 1);
1050}
1051
1052/*
1053 * In an ideal world this would be part of the XToolkit, and I would not
1054 * have to do it, but such is life sometimes.  Perhaps in X11R3.
1055 */
1056
1057/*	Function Name: PositionCenter
1058 *	Description: This function positions the given widgets center
1059 *                   in the following location.
1060 *	Arguments: widget - the widget widget to position
1061 *                 x,y - The location for the center of the widget
1062 *                 above - number of pixels above center to locate this widget
1063 *                 left - number of pixels left of center to locate this widget
1064 *                 h_space, v_space - how close to get to the edges of the
1065 *                                    parent window.
1066 *	Returns: none
1067 *      Note:  This should only be used with a popup widget that has override
1068 *             redirect set.
1069 */
1070
1071void
1072PositionCenter(Widget widget, int x, int y, int above, int left, int v_space,
1073               int h_space)
1074{
1075    Arg wargs[2];
1076    int x_temp, y_temp;         /* location of the new window. */
1077    int parent_height, parent_width;    /* Height and width of the parent widget or
1078                                           the root window if it has no parent. */
1079
1080    x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
1081    y_temp = y - above - Height(widget) / 2 + BorderWidth(widget);
1082
1083    parent_height = HeightOfScreen(XtScreen(widget));
1084    parent_width = WidthOfScreen(XtScreen(widget));
1085
1086/*
1087 * Check to make sure that all edges are within the viewable part of the
1088 * root window, and if not then force them to be.
1089 */
1090
1091    if (x_temp < h_space)
1092        x_temp = v_space;
1093    if (y_temp < v_space)
1094        (y_temp = 2);
1095
1096    if (y_temp + Height(widget) + v_space > parent_height)
1097        y_temp = parent_height - Height(widget) - v_space;
1098
1099    if (x_temp + Width(widget) + h_space > parent_width)
1100        x_temp = parent_width - Width(widget) - h_space;
1101
1102    XtSetArg(wargs[0], XtNx, x_temp);
1103    XtSetArg(wargs[1], XtNy, y_temp);
1104    XtSetValues(widget, wargs, 2);
1105}
1106
1107/*	Function Name: ParseEntry(entry, path, sect, page)
1108 *	Description: Parses the manual pages entry filenames.
1109 *	Arguments: str - the full path name.
1110 *                 path - the path name.      RETURNED
1111 *                 sect - the section name.   RETURNED
1112 *                 page - the page name.      RETURNED
1113 *	Returns: none.
1114 */
1115
1116void
1117ParseEntry(const char *entry, char *path, char *sect, char *page)
1118{
1119    char *c, temp[BUFSIZ];
1120
1121    strcpy(temp, entry);
1122
1123    c = strrchr(temp, '/');
1124    if (c == NULL)
1125        PrintError("Failed to find / in ParseEntry.");
1126    *c++ = '\0';
1127    if (page != NULL)
1128        strcpy(page, c);
1129
1130    c = strrchr(temp, '/');
1131    if (c == NULL)
1132        PrintError("Failed to find / in ParseEntry.");
1133    *c++ = '\0';
1134#if defined(SFORMAT) && defined(SMAN)
1135    /* sgmltoroff sometimes puts an extra ./ in the path to .so entries */
1136    if (strcmp(c, ".") == 0) {
1137        c = strrchr(temp, '/');
1138        if (c == NULL)
1139            PrintError("Failed to find / in ParseEntry.");
1140        *c++ = '\0';
1141    }
1142#endif
1143#if defined(__OpenBSD__) || defined(__NetBSD__)
1144    /* Skip machine subdirectory if present */
1145    if (strcmp(c, MACHINE) == 0) {
1146        c = strrchr(temp, '/');
1147        if (c == NULL)
1148            PrintError("Failed to find / in ParseEntry.");
1149        *c++ = '\0';
1150    }
1151#endif
1152    if (sect != NULL)
1153        strcpy(sect, c);
1154
1155    if (path != NULL)
1156        strcpy(path, temp);
1157}
1158
1159/*      Function Name: GetGlobals
1160 *      Description: Gets the pseudo globals associated with the
1161 *                   manpage associated with this widget.
1162 *      Arguments: w - a widget in the manpage.
1163 *      Returns: the pseudo globals.
1164 *      Notes: initial_widget is a globals variable.
1165 *             manglobals_context is a global variable.
1166 */
1167
1168ManpageGlobals *
1169GetGlobals(Widget w)
1170{
1171    Widget temp;
1172    caddr_t data;
1173
1174    while ((temp = XtParent(w)) != initial_widget && (temp != NULL))
1175        w = temp;
1176
1177    if (temp == NULL)
1178        XtAppError(XtWidgetToApplicationContext(w),
1179                   "Xman: Could not locate widget in tree, exiting");
1180
1181    if (XFindContext(XtDisplay(w), XtWindow(w),
1182                     manglobals_context, &data) != XCSUCCESS)
1183        XtAppError(XtWidgetToApplicationContext(w),
1184                   "Xman: Could not find global data, exiting");
1185
1186    return ((ManpageGlobals *) data);
1187}
1188
1189/*      Function Name: SaveGlobals
1190 *      Description: Saves the pseudo globals on the widget passed
1191 *                   to this function, although GetGlobals assumes that
1192 *                   the data is associated with the popup child of topBox.
1193 *      Arguments: w - the widget to associate the data with.
1194 *                 globals - data to associate with this widget.
1195 *      Returns: none.
1196 *      Notes: WIDGET MUST BE REALIZED.
1197 *             manglobals_context is a global variable.
1198 */
1199
1200void
1201SaveGlobals(Widget w, ManpageGlobals * globals)
1202{
1203    if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
1204                     (caddr_t) globals) != XCSUCCESS)
1205        XtAppError(XtWidgetToApplicationContext(w),
1206                   "Xman: Could not save global data, are you out of memory?");
1207}
1208
1209/*      Function Name: RemoveGlobals
1210 *      Description: Removes the pseudo globals from the widget passed
1211 *                   to this function.
1212 *      Arguments: w - the widget to remove the data from.
1213 *      Returns: none.
1214 *      Notes: WIDGET MUST BE REALIZED.
1215 *             manglobals_context is a global variable.
1216 */
1217
1218void
1219RemoveGlobals(Widget w)
1220{
1221    if (XDeleteContext(XtDisplay(w), XtWindow(w),
1222                       manglobals_context) != XCSUCCESS)
1223        XtAppError(XtWidgetToApplicationContext(w),
1224                   "Xman: Could not remove global data?");
1225}
1226