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