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