misc.c revision 189427b7
1/* $XConsortium: misc.c,v 1.31 94/12/16 21:36:53 gildea Exp $ */
2/* $XdotOrg: xc/programs/xman/misc.c,v 1.6 2004/09/02 08:40:33 kem Exp $ */
3/*
4
5Copyright (c) 1987, 1988  X Consortium
6
7Permission is hereby granted, free of charge, to any person obtaining
8a copy of this software and associated documentation files (the
9"Software"), to deal in the Software without restriction, including
10without limitation the rights to use, copy, modify, merge, publish,
11distribute, sublicense, and/or sell copies of the Software, and to
12permit persons to whom the Software is furnished to do so, subject to
13the following conditions:
14
15The above copyright notice and this permission notice shall be included
16in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
22OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24OTHER DEALINGS IN THE SOFTWARE.
25
26Except as contained in this notice, the name of the X Consortium shall
27not be used in advertising or otherwise to promote the sale, use or
28other dealings in this Software without prior written authorization
29from the X Consortium.
30
31*/
32/* $XFree86: xc/programs/xman/misc.c,v 1.10 2003/08/02 17:35:48 herrb Exp $ */
33
34/*
35 * xman - X window system manual page display program.
36 * Author:    Chris D. Peterson, MIT Project Athena
37 * Created:   October 27, 1987
38 */
39
40#ifdef HAVE_CONFIG_H
41# include "config.h"
42#endif
43
44#include "globals.h"
45#include "vendor.h"
46#include <X11/Xos.h> 		/* sys/types.h and unistd.h included in here */
47#include <sys/stat.h>
48#include <errno.h>
49#include <X11/Xaw/Dialog.h>
50#include <X11/Shell.h>
51
52static FILE * Uncompress(ManpageGlobals * man_globals, char * filename);
53#ifndef HAS_MKSTEMP
54static Boolean UncompressNamed(ManpageGlobals * man_globals, char * filename,
55			       char * output);
56static Boolean UncompressUnformatted(ManpageGlobals * man_globals,
57				     char * entry, char * filename);
58#else
59static Boolean UncompressNamed(ManpageGlobals * man_globals, char * filename,
60			       char * output, FILE ** output_fd);
61static Boolean UncompressUnformatted(ManpageGlobals * man_globals,
62				     char * entry, char * filename,
63				     FILE **file);
64#endif
65#ifdef HANDLE_ROFFSEQ
66static Boolean ConstructCommand(char * cmdbuf, char * path, char * filename, char * tempfile);
67#endif
68
69#if defined(ISC) || defined(__SCO__) || defined(__UNIXWARE__)
70static char *uncompress_format = NULL;
71static char *uncompress_formats[] =
72      {  UNCOMPRESS_FORMAT_1,
73         UNCOMPRESS_FORMAT_2,
74         UNCOMPRESS_FORMAT_3
75      };
76#endif
77
78/*	Function Name: PopupWarning
79 *	Description: This function pops upa warning message.
80 *	Arguments: string - the specific warning string.
81 *	Returns: none
82 */
83
84static Widget warnShell, warnDialog;
85
86static void
87PopdownWarning(Widget w, XtPointer client, XtPointer call)
88{
89  XtPopdown((Widget)client);
90}
91
92void
93PopupWarning(ManpageGlobals * man_globals, char * string)
94{
95  int n;
96  Arg wargs[3];
97  Dimension topX, topY;
98  char buffer[BUFSIZ];
99  Boolean hasPosition;
100
101  snprintf( buffer, sizeof(buffer), "Xman Warning: %s", string);
102  hasPosition = FALSE;
103  if (top)
104  {
105    n=0;
106    XtSetArg(wargs[n], XtNx, &topX); n++;
107    XtSetArg(wargs[n], XtNy, &topY); n++;
108    XtGetValues(top, wargs, n);
109    hasPosition = TRUE;
110  }
111
112  if (man_globals != NULL)
113    ChangeLabel(man_globals->label, buffer);
114  if (man_globals->label == NULL) {
115    n=0;
116    if (hasPosition)
117    {
118      XtSetArg(wargs[n], XtNx, topX); n++;
119      XtSetArg(wargs[n], XtNy, topY); n++;
120    }
121    XtSetArg(wargs[n], XtNtransientFor, top); n++;
122    warnShell = XtCreatePopupShell("warnShell", transientShellWidgetClass,
123				   initial_widget, wargs, n);
124    XtSetArg(wargs[0], XtNlabel, buffer);
125    warnDialog = XtCreateManagedWidget("warnDialog", dialogWidgetClass,
126				       warnShell, wargs, 1);
127    XawDialogAddButton(warnDialog, "dismiss", PopdownWarning,
128		       (XtPointer)warnShell);
129    XtRealizeWidget(warnShell);
130    Popup(warnShell, XtGrabNone);
131  }
132}
133
134/*	Function Name: PrintError
135 *	Description: This Function prints an error message and exits.
136 *	Arguments: string - the specific message.
137 *	Returns: none. - exits tho.
138 */
139
140void
141PrintError(char * string)
142{
143  fprintf(stderr,"Xman Error: %s\n",string);
144  exit(EXIT_FAILURE);
145}
146
147/*	Function Name: OpenFile
148 *	Description: Assignes a file to the manpage.
149 *	Arguments: man_globals - global structure.
150 *                 file        - the file pointer.
151 *	Returns: none
152 */
153
154void
155OpenFile(ManpageGlobals * man_globals, FILE * file)
156{
157  Arg arglist[1];
158  Cardinal num_args = 0;
159
160  if (man_globals->curr_file) {
161#if 0 /* Ownership rules need to be fixed first */
162    fclose(man_globals->curr_file);
163#endif
164  }
165  man_globals->curr_file = file;
166
167  XtSetArg(arglist[num_args], XtNfile, man_globals->curr_file); num_args++;
168  XtSetValues(man_globals->manpagewidgets.manpage, arglist, num_args);
169}
170
171
172/*	Function Name: FindManualFile
173 *	Description: Opens the manual page file given the entry information.
174 *	Arguments: man_globals - the globals info for this manpage.
175 *                 section_num - section number of the man page.
176 *                 entry_num   - entry number of the man page.
177 *	Returns: fp - the file pointer
178 *
179 * NOTES:
180 *
181 * If there is a uncompressed section it will look there for uncompresed
182 * manual pages first and then for individually compressed file in the
183 * uncompressed section.
184 *
185 * If there is a compressed directory then it will also look there for
186 * the manual pages.
187 *
188 * If both of these fail then it will attempt to format the manual page.
189 */
190
191FILE *
192FindManualFile(ManpageGlobals * man_globals, int section_num, int entry_num)
193{
194  FILE * file;
195  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], *temp;
196  char filename[BUFSIZ];
197  char * entry = manual[section_num].entries[entry_num];
198  int len_cat = strlen(CAT);
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    sprintf(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    sprintf(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/*	Function Namecompress
313 *	Description: This function will attempt to find a compressed man
314 *                   page and uncompress it.
315 *	Arguments: man_globals - the psuedo global info.
316 *                 filename - name of file to uncompress.
317 *	Returns:; a pointer to the file or NULL.
318 */
319
320static FILE *
321Uncompress(ManpageGlobals * man_globals, char * filename)
322{
323  char tmp_file[BUFSIZ], error_buf[BUFSIZ];
324  FILE * file;
325
326#ifndef HAS_MKSTEMP
327  if ( !UncompressNamed(man_globals, filename, tmp_file) )
328    return(NULL);
329
330  else if ((file = fopen(tmp_file, "r")) == NULL) {
331      sprintf(error_buf, "Something went wrong in retrieving the %s",
332	      "uncompressed manual page try cleaning up /tmp.");
333      PopupWarning(man_globals, error_buf);
334  }
335#else
336  if (!UncompressNamed(man_globals, filename, tmp_file, &file)) {
337      sprintf(error_buf, "Something went wrong in retrieving the %s",
338	      "uncompressed manual page try cleaning up /tmp.");
339      PopupWarning(man_globals, error_buf);
340      return(NULL);
341  }
342#endif
343
344  unlink(tmp_file);		/* remove name in tree, it will remain
345				   until we close the fd, however. */
346  return(file);
347}
348
349/*	Function Name: UncompressNamed
350 *	Description: This function will attempt to find a compressed man
351 *                   page and uncompress it.
352 *	Arguments: man_globals - the psuedo global info.
353 *                 filename - name of file to uncompress.
354 * RETURNED        output - the file name output (must be an allocated string).
355 *	Returns:; TRUE if the file was found.
356 */
357
358#ifndef HAS_MKSTEMP
359static Boolean
360UncompressNamed(ManpageGlobals * man_globals, char * filename, char * output)
361#else
362static Boolean
363UncompressNamed(ManpageGlobals * man_globals, char * filename, char * output,
364		FILE ** output_fd)
365#endif
366{
367  char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
368  struct stat junk;
369#ifdef HAS_MKSTEMP
370  int fd;
371#endif
372
373  if (stat(filename, &junk) != 0) { /* Check for existance of the file. */
374    if (errno != ENOENT) {
375      snprintf(error_buf, sizeof(error_buf),
376	       "Error while stating file %s, errno = %d", filename, errno);
377      PopupWarning(man_globals, error_buf);
378    }
379    return(FALSE);
380  }
381
382/*
383 * Using stdin is necessary to fool zcat since we cannot guarentee
384 * the .Z extension.
385 */
386
387  strcpy(tmp, MANTEMP);		/* get a temp file. */
388#ifndef HAS_MKSTEMP
389  (void) mktemp(tmp);
390#else
391  fd = mkstemp(tmp);
392  if (fd < 0) {
393      PopupWarning(man_globals, "Error creating a temp file");
394      return FALSE;
395  }
396  *output_fd = fdopen(fd, "r");
397#endif
398  strcpy(output, tmp);
399
400#ifdef GZIP_EXTENSION
401  if (streq(filename + strlen(filename) - strlen(GZIP_EXTENSION),
402	    GZIP_EXTENSION))
403    snprintf(cmdbuf, sizeof(cmdbuf), GUNZIP_FORMAT, filename, output);
404  else
405#endif
406#ifdef BZIP2_EXTENSION
407  if (streq(filename + strlen(filename) - strlen(BZIP2_EXTENSION),
408	    BZIP2_EXTENSION))
409    sprintf(cmdbuf, BUNZIP2_FORMAT, filename, output);
410  else
411#endif
412#ifdef LZMA_EXTENSION
413  if (streq(filename + strlen(filename) - strlen(LZMA_EXTENSION),
414	    LZMA_EXTENSION))
415    sprintf(cmdbuf, UNLZMA_FORMAT, filename, output);
416  else
417#endif
418  snprintf(cmdbuf, sizeof(cmdbuf), UNCOMPRESS_FORMAT, filename, output);
419  if(system(cmdbuf) == 0) 	/* execute search. */
420    return(TRUE);
421
422  snprintf(error_buf, sizeof(error_buf),
423	   "Error while uncompressing, command was: %s", cmdbuf);
424  PopupWarning(man_globals, error_buf);
425  return(FALSE);
426}
427
428#if defined(SMAN) && defined(SFORMAT)
429/*	Function Name: SgmlToRoffNamed
430 *	Description: This function will attempt to find an SGML man
431 *                   page and convert it to roff format.
432 *	Arguments: man_globals - the psuedo global info.
433 *                 filename - name of file to uncompress.
434 * RETURNED        output - the file name output (must be an allocated string).
435 *	Returns:; TRUE if the file was found.
436 */
437
438#ifndef HAS_MKSTEMP
439static Boolean
440SgmlToRoffNamed(ManpageGlobals * man_globals, char * filename, char * output)
441#else
442static Boolean
443SgmlToRoffNamed(ManpageGlobals * man_globals, char * filename, char * output,
444		FILE ** output_fd)
445#endif
446{
447  char tmp[BUFSIZ], cmdbuf[BUFSIZ], error_buf[BUFSIZ];
448  struct stat junk;
449#ifdef HAS_MKSTEMP
450  int fd;
451#endif
452
453  if (stat(filename, &junk) != 0) { /* Check for existance of the file. */
454    if (errno != ENOENT) {
455      snprintf(error_buf, sizeof(error_buf),
456	       "Error while stating file %s, errno = %d", filename, errno);
457      PopupWarning(man_globals, error_buf);
458    }
459    return(FALSE);
460  }
461
462  strcpy(tmp, MANTEMP);		/* get a temp file. */
463#ifndef HAS_MKSTEMP
464  (void) mktemp(tmp);
465#else
466  fd = mkstemp(tmp);
467  if (fd < 0) {
468      PopupWarning(man_globals, "Error creating a temp file");
469      return FALSE;
470  }
471  *output_fd = fdopen(fd, "r");
472#endif
473  strcpy(output, tmp);
474
475  snprintf(cmdbuf, sizeof(cmdbuf), "%s %s > %s", SFORMAT, filename, output);
476  if(system(cmdbuf) == 0) 	/* execute search. */
477    return(TRUE);
478
479  snprintf(error_buf, sizeof(error_buf),
480	   "Error while converting from sgml, command was: %s", cmdbuf);
481  PopupWarning(man_globals, error_buf);
482  return(FALSE);
483}
484#endif /* defined (SMAN) && defined(SFORMAT) */
485
486/*	Function Name: Format
487 *	Description: This funtion formats the manual pages and interfaces
488 *                   with the user.
489 *	Arguments: man_globals - the psuedo globals
490 *                 file - the file pointer to use and return
491 *                 entry - the current entry struct.
492 *                 current_box - The current directory being displayed.
493 *	Returns: none.
494 */
495
496/* ARGSUSED */
497
498FILE *
499Format(ManpageGlobals * man_globals, char * entry)
500{
501  FILE * file = NULL;
502#ifdef HAS_MKSTEMP
503  int fd;
504#endif
505  Widget manpage = man_globals->manpagewidgets.manpage;
506  char cmdbuf[BUFSIZ], tmp[BUFSIZ], filename[BUFSIZ], error_buf[BUFSIZ];
507  char path[BUFSIZ], sect[BUFSIZ];
508  XEvent event;
509  Position x,y;			/* location to pop up the
510				   "would you like to save" widget. */
511
512#ifndef HAS_MKSTEMP
513  if ( !UncompressUnformatted(man_globals, entry, filename) ) {
514#else
515  if ( !UncompressUnformatted(man_globals, entry, filename, &file) ) {
516#endif
517    /* We Really could not find it, this should never happen, yea right. */
518    snprintf(error_buf, sizeof(error_buf),
519	     "Could not open manual page, %s", entry);
520    PopupWarning(man_globals, error_buf);
521    XtPopdown( XtParent(man_globals->standby) );
522    return(NULL);
523  }
524
525#ifndef HAS_MKSTEMP
526  if ((file = fopen(filename, "r")) != NULL) {
527#else
528  if (file != NULL) {
529#endif
530    char line[BUFSIZ];
531
532    if (fgets(line, sizeof(line), file) != NULL) {
533	if (strncmp(line, ".so ", 4) == 0) {
534	  line[strlen(line) - 1] = '\0';
535	  fclose(file);
536	  unlink(filename);
537	  if (line[4] != '/') {
538	    char *ptr = NULL;
539
540	    strcpy(tmp, entry);
541	    if ((ptr = rindex(tmp, '/')) != NULL) {
542	      *ptr = '\0';
543	      if ((ptr = rindex(tmp, '/')) != NULL)
544		ptr[1] = '\0';
545	    }
546	  }
547	  else
548	    *tmp = '\0';
549	  snprintf(filename, sizeof(filename), "%s%s", tmp, line + 4);
550
551	  return (Format(man_globals, filename));
552	}
553    }
554    fclose(file);
555  }
556
557  Popup(XtParent(man_globals->standby), XtGrabExclusive);
558  while ( !XCheckTypedWindowEvent(XtDisplay(man_globals->standby),
559				  XtWindow(man_globals->standby),
560				  Expose, &event) );
561  XtDispatchEvent( &event );
562  XFlush(XtDisplay(man_globals->standby));
563
564  strcpy(tmp,MANTEMP);		          /* Get a temp file. */
565#ifndef HAS_MKSTEMP
566  (void) mktemp(tmp);
567#else
568  fd = mkstemp(tmp);
569  file = fdopen(fd, "r");
570#endif
571  strcpy(man_globals->tempfile, tmp);
572
573  ParseEntry(entry, path, sect, NULL);
574
575#ifndef HANDLE_ROFFSEQ
576#ifndef HAS_MKSTEMP
577  snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s > %s %s", path, TBL,
578	  filename, FORMAT, man_globals->tempfile, "2> /dev/null");
579#else
580  snprintf(cmdbuf, sizeof(cmdbuf), "cd %s ; %s %s %s >> %s %s", path, TBL,
581	  filename, FORMAT, man_globals->tempfile, "2> /dev/null");
582#endif
583#else
584  /* Handle more flexible way of specifying the formatting pipeline */
585  if (! ConstructCommand(cmdbuf, path, filename, man_globals->tempfile)) {
586     sprintf(error_buf, "Constructed command was too long!");
587     PopupWarning(man_globals, error_buf);
588     file = NULL;
589  }
590  else
591#endif /* HANDLE_ROFFSEQ */
592
593  if(system(cmdbuf) != 0) {	/* execute search. */
594    snprintf(error_buf, sizeof(error_buf),
595	    "Something went wrong trying to run the command: %s", cmdbuf);
596    PopupWarning(man_globals, error_buf);
597    file = NULL;
598  }
599  else {
600#ifndef HAS_MKSTEMP
601    if ((file = fopen(man_globals->tempfile,"r")) == NULL) {
602      sprintf(error_buf, "Something went wrong in retrieving the %s",
603	      "temp file, try cleaning up /tmp");
604      PopupWarning(man_globals, error_buf);
605    }
606    else {
607#endif
608
609      XtPopdown( XtParent(man_globals->standby) );
610
611      if ( (man_globals->save == NULL) ||
612	   (man_globals->manpagewidgets.manpage == NULL) )
613	unlink(man_globals->tempfile);
614      else {
615	char * ptr, catdir[BUFSIZ];
616
617	/*
618	 * If the catdir is writeable 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 = rindex(catdir, '/')) != NULL) {
624	  *ptr = '\0';
625
626	  if ( access(catdir, W_OK) != 0 )
627	    unlink(man_globals->tempfile);
628	  else {
629	    x = (Position) Width(man_globals->manpagewidgets.manpage)/2;
630	    y = (Position) Height(man_globals->manpagewidgets.manpage)/2;
631	    XtTranslateCoords(manpage, x, y, &x, &y);
632	    PositionCenter( man_globals->save, (int) x, (int) y, 0, 0, 0, 0);
633	    XtPopup( man_globals->save, XtGrabExclusive);
634	  }
635	}
636	else
637	  unlink(man_globals->tempfile);
638      }
639#ifndef HAS_MKSTEMP
640    }
641#endif
642  }
643
644 /*
645  * If the original was compressed or in another format, delete temporary file.
646  */
647  if (man_globals->deletetempfile)
648    unlink(filename);
649
650  return(file);
651}
652
653#ifdef HANDLE_ROFFSEQ
654/*      Function Name: ConstructCommand
655 *      Description: Constructs the pipeline of commands necessary to format
656 *                   a manual page.
657 *      Arguments: cmdbuf - the buffer into which to write the command
658 *                 path - the directory in which the original man page resides
659 *                 filename - the (uncompressed) manpage source file
660 *                 tempfile - the name of a temporary file to direct the final
661 *                  output of the pipeline into
662 *      Returns: TRUE if the command fit into the buffer, FALSE if it would
663 *               be too long (more than BUFSIZ characters)
664 */
665static Boolean
666ConstructCommand(cmdbuf, path, filename, tempfile)
667   char *cmdbuf, *path, *filename, *tempfile;
668{
669   /* The original code did the following to produce a command line:
670    *   sprintf(cmdbuf,"cd %s ; %s %s %s > %s %s", path, TBL,
671    *      filename, FORMAT, man_globals->tempfile, "2> /dev/null");
672    * We are more flexible and follow more or less the algorithm used
673    * by the Linux man command:
674    *  + Obtain a string of letters from the following sources in order
675    *    of preference:
676    *    + a command line option (not implemented in xman; it's probably not
677    *      useful)
678    *    + the first line of the manpage source, if it is of the form:
679    *      '\" <string>
680    *    + the MANROFFSEQ environment variable
681    *    + a default string; this is "".
682    *  + Interpret the string as a pipeline of filters:
683    *    + e = eqn   g = grap   p = pic   t = tbl   v = vgrind   r = refer
684    *  + zsoelim is always run as the first preprocessor in any case.
685    *
686    * Strictly speaking we should save a catpage iff the string comes
687    * from the file or is the default.
688    *
689    * You'll notice that we format a man page into ASCII text output and then
690    * attempt to interpret things like L^HL as bold and so forth. This
691    * is so obviously the Wrong Thing it's untrue.
692    */
693   char *c = cmdbuf;           /* current posn in buffer */
694   int left = BUFSIZ;          /* space left in buffer */
695   int used;
696   char *fmt;
697   FILE *file;
698   char fmtbuf[128];
699   int gotfmt = 0;             /* set to 1 if we got a directive from source */
700   char *fname = NULL;
701#ifdef __UNIXOS2__
702   int i;
703#endif
704
705   fmt = NULL;
706   /* If you have a command line option that gives a setting for fmt,
707      set it here. */
708
709   if (!fmt) {
710      /* This is the tricky bit: extract a format string from the source file
711       * Annoyingly, filename might be relative or absolute. We cheat and
712       * use system to get the thing to a known absoute filename.
713       */
714      if (filename[0] == '/') {
715         fname = filename;
716      } else {
717         fname = malloc(strlen(path) + 1 + strlen(filename) + 1);
718         if (!fname)
719            return FALSE;
720         sprintf(fname, "%s/%s", path, filename);
721      }
722      if ((file = fopen(fname, "r")) &&
723          (fgets(fmtbuf, sizeof(fmtbuf), file)) &&
724          (!memcmp(fmtbuf, "'\\\" ", 4))) {
725                              /* that's squote-backslash-dquote-space */
726         int len;
727         fmt = fmtbuf + 3;
728         len = strlen(fmt);
729         if (len && (fmt[len-1] == '\n')) {
730            fmt[len-1] = 0;
731            gotfmt++;
732         }
733      }
734      if (fname && fname != filename)
735         free(fname);
736      if (!gotfmt)                                /* not there or some error */
737      {
738         fmt = getenv("MANROFFSEQ");
739      }
740   }
741
742   if (!fmt)
743   {
744      fmt = DEFAULT_MANROFFSEQ;
745   }
746
747
748   /* Start with the first fixed part of the command line */
749#ifdef __UNIXOS2__
750   for (i = 0; i < strlen(path); i++) {
751     if (path[i] == '/')
752       path[i] = '\\';
753   }
754   used = snprintf(c, left, "cd %s & %s %s ", path, ZSOELIM, filename);
755#else
756   used = snprintf(c, left, "cd %s; %s %s ", path, ZSOELIM, filename);
757#endif
758   left -= used;
759   c += used;
760   if (left <= 1)
761      return (FALSE);
762
763   /* Now add preprocessors of the form '| processor' */
764   for ( ; *fmt; fmt++)
765   {
766      char *filter;
767      switch (*fmt)
768      {
769         case 'e':
770            filter = EQN;
771            break;
772         case 'g':
773            filter = GRAP;
774            break;
775         case 'p':
776            filter = XMAN_PIC;
777            break;
778         case 't':
779            filter = TBL;
780            break;
781         case 'v':
782            filter = VGRIND;
783            break;
784         case 'r':
785            filter = REFER;
786            break;
787         default:
788            filter = NULL;
789            break;
790      }
791      if (filter)
792      {
793         used = snprintf(c, left, " | %s ", filter);
794         left -= used;
795         c += used;
796         if (left <= 1)
797            return (FALSE);
798      }
799   }
800
801   /* Now add the fixed trailing part 'formatprog > tempfile 2> /dev/null' */
802#ifdef __UNIXOS2__
803   used = snprintf(c, left, " | %s > %s 2>NUL", FORMAT, tempfile);
804#else
805#ifndef HAS_MKSTEMP
806   used = snprintf(c, left, " | %s > %s 2>/dev/null", FORMAT, tempfile);
807#else
808   used = snprintf(c, left, " | %s >> %s 2>/dev/null", FORMAT, tempfile);
809#endif
810#endif /* __UNIXOS2__ */
811   left -= used;
812   if (left <= 1)
813      return (FALSE);
814
815   return (TRUE);
816}
817#endif /* HANDLE_ROFFSEQ */
818
819/*	Function Name: UncompressUnformatted
820 *	Description: Finds an uncompressed unformatted manual page.
821 *	Arguments: man_globals - the psuedo global structure.
822 *                 entry - the manual page entry.
823 * RETURNED        filename - location to put the name of the file.
824 *	Returns: TRUE if the file was found.
825 */
826
827static Boolean
828#ifndef HAS_MKSTEMP
829UncompressUnformatted(ManpageGlobals * man_globals, char * entry,
830		      char * filename)
831#else
832UncompressUnformatted(ManpageGlobals * man_globals, char * entry,
833		      char * filename, FILE **file)
834#endif
835{
836  char path[BUFSIZ], page[BUFSIZ], section[BUFSIZ], input[BUFSIZ];
837  int len_cat = strlen(CAT), len_man = strlen(MAN);
838#if defined(SMAN) && defined(SFORMAT)
839  int len_sman = strlen(SMAN);
840#endif
841
842  ParseEntry(entry, path, section, page);
843
844  man_globals->bzip2 = FALSE;
845  man_globals->lzma = FALSE;
846
847#if defined(__OpenBSD__) || defined(__NetBSD__)
848  /*
849   * look for uncompressed file in machine subdir first
850   */
851  snprintf(filename, BUFSIZ, "%s/%s%s/%s/%s", path, MAN,
852	  section + len_cat, MACHINE, page);
853  if ( access( filename, R_OK ) == 0 ) {
854    man_globals->compress = FALSE;
855    man_globals->gzip = FALSE;
856    man_globals->deletetempfile = FALSE;
857    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
858    	     "%s/%s%s/%s/%s", path, CAT, section + len_cat, MACHINE, page);
859    return(TRUE);
860  }
861 /*
862  * Then for compressed files in an uncompressed directory.
863  */
864  snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
865#ifndef HAS_MKSTEMP
866  if ( UncompressNamed(man_globals, input, filename) ) {
867#else
868  if ( UncompressNamed(man_globals, input, filename, file) ) {
869#endif
870    man_globals->compress = TRUE;
871    man_globals->deletetempfile = TRUE;
872    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
873	     "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
874	     COMPRESSION_EXTENSION);
875    return(TRUE);
876  }
877#ifdef GZIP_EXTENSION
878  else {
879    snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
880#ifndef HAS_MKSTEMP
881    if ( UncompressNamed(man_globals, input, filename) ) {
882#else
883    if ( UncompressNamed(man_globals, input, filename, file) ) {
884#endif
885      man_globals->compress = TRUE;
886      man_globals->gzip = TRUE;
887      man_globals->deletetempfile = TRUE;
888      snprintf(man_globals->save_file, sizeof(man_globals->save_file),
889	       "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
890	       GZIP_EXTENSION);
891      return(TRUE);
892    }
893  }
894#endif /* GZIP_EXTENSION */
895#endif /* __OpenBSD__ || __NetBSD__ */
896
897#ifdef BZIP2_EXTENSION
898 {
899    sprintf(input, "%s.%s", filename, BZIP2_EXTENSION);
900#ifndef HAS_MKSTEMP
901    if ( UncompressNamed(man_globals, input, filename) ) {
902#else
903    if ( UncompressNamed(man_globals, input, filename, file) ) {
904#endif
905      man_globals->compress = TRUE;
906      man_globals->gzip = FALSE;
907      man_globals->bzip2 = TRUE;
908      sprintf(man_globals->save_file, "%s/%s%s/%s.%s", path,
909	      CAT, section + len_cat, page, BZIP2_EXTENSION);
910      return(TRUE);
911    }
912  }
913#endif /* BZIP2_EXTENSION */
914
915#ifdef LZMA_EXTENSION
916 {
917    sprintf(input, "%s.%s", filename, LZMA_EXTENSION);
918#ifndef HAS_MKSTEMP
919    if ( UncompressNamed(man_globals, input, filename) ) {
920#else
921    if ( UncompressNamed(man_globals, input, filename, file) ) {
922#endif
923      man_globals->compress = TRUE;
924      man_globals->gzip = FALSE;
925      man_globals->lzma = TRUE;
926      sprintf(man_globals->save_file, "%s/%s%s/%s.%s", path,
927	      CAT, section + len_cat, page, LZMA_EXTENSION);
928      return(TRUE);
929    }
930  }
931#endif /* LZMA_EXTENSION */
932
933/*
934 * Look for uncompressed file first.
935 */
936
937  snprintf(filename, BUFSIZ, "%s/%s%s/%s", path, MAN, section + len_man, page);
938  if ( access( filename, R_OK ) == 0 ) {
939    man_globals->compress = FALSE;
940    man_globals->gzip = FALSE;
941    man_globals->deletetempfile = FALSE;
942    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
943	     "%s/%s%s/%s", path, CAT, section + len_cat, page);
944    return(TRUE);
945  }
946
947#if defined(SMAN) && defined(SFORMAT)
948 /*
949  * Look for uncompressed sgml file next.
950  */
951
952  snprintf(input, BUFSIZ, "%s/%s%s/%s", path, SMAN, section + len_sman, page);
953#ifndef HAS_MKSTEMP
954  if ( SgmlToRoffNamed(man_globals, input, filename) ) {
955#else
956  if ( SgmlToRoffNamed(man_globals, input, filename, file) ) {
957#endif
958    man_globals->compress = FALSE;
959    man_globals->gzip = FALSE;
960    man_globals->deletetempfile = TRUE;
961    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
962            "%s/%s%s/%s", path, CAT, section + len_cat, page);
963    return(TRUE);
964  }
965#endif
966
967/*
968 * Then for compressed files in an uncompressed directory.
969 */
970
971  snprintf(input, sizeof(input), "%s.%s", filename, COMPRESSION_EXTENSION);
972#ifndef HAS_MKSTEMP
973  if ( UncompressNamed(man_globals, input, filename) ) {
974#else
975  if ( UncompressNamed(man_globals, input, filename, file) ) {
976#endif
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, page,
981	     COMPRESSION_EXTENSION);
982    return(TRUE);
983  }
984#ifdef GZIP_EXTENSION
985  else {
986    snprintf(input, sizeof(input), "%s.%s", filename, GZIP_EXTENSION);
987#ifndef HAS_MKSTEMP
988    if ( UncompressNamed(man_globals, input, filename) ) {
989#else
990    if ( UncompressNamed(man_globals, input, filename, file) ) {
991#endif
992      man_globals->compress = TRUE;
993      man_globals->gzip = TRUE;
994      man_globals->deletetempfile = TRUE;
995      snprintf(man_globals->save_file, sizeof(man_globals->save_file),
996	       "%s/%s%s/%s.%s", path, CAT, section + len_cat, page,
997	       GZIP_EXTENSION);
998      return(TRUE);
999    }
1000  }
1001#endif
1002
1003#ifdef BZIP2_EXTENSION
1004  {
1005    sprintf(input, "%s.%s", filename, BZIP2_EXTENSION);
1006#ifndef HAS_MKSTEMP
1007    if ( UncompressNamed(man_globals, input, filename) ) {
1008#else
1009    if ( UncompressNamed(man_globals, input, filename, file) ) {
1010#endif
1011      man_globals->compress = TRUE;
1012      man_globals->gzip = TRUE;
1013      sprintf(man_globals->save_file, "%s/%s%s/%s.%s", path,
1014	      CAT, section + len_cat, page, BZIP2_EXTENSION);
1015      return(TRUE);
1016    }
1017  }
1018#endif
1019
1020#ifdef LZMA_EXTENSION
1021  {
1022    sprintf(input, "%s.%s", filename, LZMA_EXTENSION);
1023#ifndef HAS_MKSTEMP
1024    if ( UncompressNamed(man_globals, input, filename) ) {
1025#else
1026    if ( UncompressNamed(man_globals, input, filename, file) ) {
1027#endif
1028      man_globals->compress = TRUE;
1029      man_globals->lzma = TRUE;
1030      sprintf(man_globals->save_file, "%s/%s%s/%s.%s", path,
1031	      CAT, section + len_cat, page, LZMA_EXTENSION);
1032      return(TRUE);
1033    }
1034  }
1035#endif
1036
1037/*
1038 * And lastly files in a compressed directory.
1039 */
1040
1041  snprintf(input, sizeof(input), "%s/%s%s.%s/%s", path,
1042	  MAN, section + len_man, COMPRESSION_EXTENSION, page);
1043#ifndef HAS_MKSTEMP
1044  if ( UncompressNamed(man_globals, input, filename) ) {
1045#else
1046  if ( UncompressNamed(man_globals, input, filename, file) ) {
1047#endif
1048    man_globals->compress = TRUE;
1049    man_globals->deletetempfile = TRUE;
1050    snprintf(man_globals->save_file, sizeof(man_globals->save_file),
1051	     "%s/%s%s.%s/%s", path, CAT, section + len_cat,
1052    	     COMPRESSION_EXTENSION, page);
1053    return(TRUE);
1054  }
1055  return(FALSE);
1056}
1057
1058/*	Function Name: AddCursor
1059 *	Description: This function adds the cursor to the window.
1060 *	Arguments: w - the widget to add the cursor to.
1061 *                 cursor - the cursor to add to this widget.
1062 *	Returns: none
1063 */
1064
1065void
1066AddCursor(Widget w, Cursor cursor)
1067{
1068  XColor colors[2];
1069  Arg args[10];
1070  Cardinal num_args = 0;
1071  Colormap c_map;
1072
1073  if (!XtIsRealized(w)) {
1074    PopupWarning(NULL, "Widget is not realized, no cursor added.\n");
1075    return;
1076  }
1077
1078  XtSetArg( args[num_args], XtNcolormap, &c_map); num_args++;
1079  XtGetValues( w, args, num_args);
1080
1081  colors[0].pixel = resources.cursors.fg_color;
1082  colors[1].pixel = resources.cursors.bg_color;
1083
1084  XQueryColors (XtDisplay(w), c_map, colors, 2);
1085  XRecolorCursor(XtDisplay(w), cursor, colors, colors+1);
1086  XDefineCursor(XtDisplay(w),XtWindow(w),cursor);
1087}
1088
1089/*	Function Name: ChangeLabel
1090 *	Description: This function changes the label field of the
1091 *                   given widget to the string in str.
1092 *	Arguments: w - the widget.
1093 *                 str - the string to change the label to.
1094 *	Returns: none
1095 */
1096
1097void
1098ChangeLabel(Widget w, char * str)
1099{
1100  Arg arglist[3];		/* An argument list. */
1101
1102  if (w == NULL) return;
1103
1104  XtSetArg(arglist[0], XtNlabel, str);
1105
1106/* shouldn't really have to do this. */
1107  XtSetArg(arglist[1], XtNwidth, 0);
1108  XtSetArg(arglist[2], XtNheight, 0);
1109
1110  XtSetValues(w, arglist, (Cardinal) 1);
1111}
1112
1113/*
1114 * In an ideal world this would be part of the XToolkit, and I would not
1115 * have to do it, but such is life sometimes.  Perhaps in X11R3.
1116 */
1117
1118/*	Function Name: PositionCenter
1119 *	Description: This function positions the given widgets center
1120 *                   in the following location.
1121 *	Arguments: widget - the widget widget to postion
1122 *                 x,y - The location for the center of the widget
1123 *                 above - number of pixels above center to locate this widget
1124 *                 left - number of pixels left of center to locate this widget
1125 *                 h_space, v_space - how close to get to the edges of the
1126 *                                    parent window.
1127 *	Returns: none
1128 *      Note:  This should only be used with a popup widget that has override
1129 *             redirect set.
1130 */
1131
1132void
1133PositionCenter(Widget widget, int x, int y, int above, int left, int v_space, int h_space)
1134{
1135  Arg wargs[2];
1136  int x_temp,y_temp;		/* location of the new window. */
1137  int parent_height,parent_width; /* Height and width of the parent widget or
1138				   the root window if it has no parent. */
1139
1140  x_temp = x - left - Width(widget) / 2 + BorderWidth(widget);
1141  y_temp = y - above -  Height(widget) / 2 + BorderWidth(widget);
1142
1143  parent_height = HeightOfScreen(XtScreen(widget));
1144  parent_width = WidthOfScreen(XtScreen(widget));
1145
1146/*
1147 * Check to make sure that all edges are within the viewable part of the
1148 * root window, and if not then force them to be.
1149 */
1150
1151  if (x_temp < h_space)
1152    x_temp = v_space;
1153  if (y_temp < v_space)
1154    (y_temp = 2);
1155
1156  if ( y_temp + Height(widget) + v_space > parent_height )
1157      y_temp = parent_height - Height(widget) - v_space;
1158
1159  if ( x_temp + Width(widget) + h_space > parent_width )
1160      x_temp = parent_width - Width(widget) - h_space;
1161
1162  XtSetArg(wargs[0], XtNx, x_temp);
1163  XtSetArg(wargs[1], XtNy, y_temp);
1164  XtSetValues(widget, wargs, 2);
1165}
1166
1167/*	Function Name: ParseEntry(entry, path, sect, page)
1168 *	Description: Parses the manual pages entry filenames.
1169 *	Arguments: str - the full path name.
1170 *                 path - the path name.      RETURNED
1171 *                 sect - the section name.   RETURNED
1172 *                 page - the page name.      RETURNED
1173 *	Returns: none.
1174 */
1175
1176void
1177ParseEntry(char *entry, char *path, char *sect, char *page)
1178{
1179  char *c, temp[BUFSIZ];
1180
1181  strcpy(temp, entry);
1182
1183  c = rindex(temp, '/');
1184  if (c == NULL)
1185    PrintError("index failure in ParseEntry.");
1186  *c++ = '\0';
1187  if (page != NULL)
1188    strcpy(page, c);
1189
1190  c = rindex(temp, '/');
1191  if (c == NULL)
1192    PrintError("index failure in ParseEntry.");
1193  *c++ = '\0';
1194#if defined(SFORMAT) && defined(SMAN)
1195  /* sgmltoroff sometimes puts an extra ./ in the path to .so entries */
1196  if (strcmp(c, ".") == 0) {
1197      c = rindex(temp, '/');
1198      if (c == NULL)
1199	  PrintError("index failure in ParseEntry.");
1200      *c++ = '\0';
1201  }
1202#endif
1203#if defined(__OpenBSD__) || defined(__NetBSD__)
1204  /* Skip machine subdirectory if present */
1205  if (strcmp(c, MACHINE) == 0) {
1206      c = rindex(temp, '/');
1207      if (c == NULL)
1208	  PrintError("index failure in ParseEntry.");
1209      *c++ = '\0';
1210  }
1211#endif
1212  if (sect != NULL)
1213    strcpy(sect, c);
1214
1215  if (path != NULL)
1216    strcpy(path, temp);
1217}
1218
1219/*      Function Name: GetGlobals
1220 *      Description: Gets the psuedo globals associated with the
1221 *                   manpage associated with this widget.
1222 *      Arguments: w - a widget in the manpage.
1223 *      Returns: the psuedo globals.
1224 *      Notes: initial_widget is a globals variable.
1225 *             manglobals_context is a global variable.
1226 */
1227
1228ManpageGlobals *
1229GetGlobals(Widget w)
1230{
1231  Widget temp;
1232  caddr_t data;
1233
1234  while ( (temp = XtParent(w)) != initial_widget && (temp != NULL))
1235    w = temp;
1236
1237  if (temp == NULL)
1238    XtAppError(XtWidgetToApplicationContext(w),
1239	       "Xman: Could not locate widget in tree, exiting");
1240
1241  if (XFindContext(XtDisplay(w), XtWindow(w),
1242		   manglobals_context, &data) != XCSUCCESS)
1243    XtAppError(XtWidgetToApplicationContext(w),
1244	       "Xman: Could not find global data, exiting");
1245
1246  return( (ManpageGlobals *) data);
1247}
1248
1249/*      Function Name: SaveGlobals
1250 *      Description: Saves the psuedo globals on the widget passed
1251 *                   to this function, although GetGlobals assumes that
1252 *                   the data is associated with the popup child of topBox.
1253 *      Arguments: w - the widget to associate the data with.
1254 *                 globals - data to associate with this widget.
1255 *      Returns: none.
1256 *      Notes: WIDGET MUST BE REALIZED.
1257 *             manglobals_context is a global variable.
1258 */
1259
1260void
1261SaveGlobals(Widget w, ManpageGlobals * globals)
1262{
1263  if (XSaveContext(XtDisplay(w), XtWindow(w), manglobals_context,
1264		   (caddr_t) globals) != XCSUCCESS)
1265    XtAppError(XtWidgetToApplicationContext(w),
1266	       "Xman: Could not save global data, are you out of memory?");
1267}
1268
1269/*      Function Name: RemoveGlobals
1270 *      Description: Removes the psuedo globals from the widget passed
1271 *                   to this function.
1272 *      Arguments: w - the widget to remove the data from.
1273 *      Returns: none.
1274 *      Notes: WIDGET MUST BE REALIZED.
1275 *             manglobals_context is a global variable.
1276 */
1277
1278void
1279RemoveGlobals(Widget w)
1280{
1281  if (XDeleteContext(XtDisplay(w), XtWindow(w),
1282		     manglobals_context) != XCSUCCESS)
1283    XtAppError(XtWidgetToApplicationContext(w),
1284	       "Xman: Could not remove global data?");
1285}
1286