1/*  man.c: How to read and format man files.
2    $Id: man.c,v 1.1 2004/10/28 18:14:09 zooey Exp $
3
4   Copyright (C) 1995, 97 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20   Written by Brian Fox Thu May  4 09:17:52 1995 (bfox@ai.mit.edu). */
21
22#include "info.h"
23#include <sys/ioctl.h>
24#include "signals.h"
25#if defined (HAVE_SYS_TIME_H)
26#include <sys/time.h>
27#endif
28#if defined (HAVE_SYS_WAIT_H)
29#include <sys/wait.h>
30#endif
31
32#include "tilde.h"
33#include "man.h"
34
35#if !defined (_POSIX_VERSION)
36#define pid_t int
37#endif
38
39#if defined (FD_SET)
40#  if defined (hpux)
41#    define fd_set_cast(x) (int *)(x)
42#  else
43#    define fd_set_cast(x) (fd_set *)(x)
44#  endif /* !hpux */
45#endif /* FD_SET */
46
47static char *read_from_fd ();
48static void clean_manpage ();
49static NODE *manpage_node_of_file_buffer ();
50static char *get_manpage_contents ();
51
52NODE *
53make_manpage_node (pagename)
54     char *pagename;
55{
56  return (info_get_node (MANPAGE_FILE_BUFFER_NAME, pagename));
57}
58
59NODE *
60get_manpage_node (file_buffer, pagename)
61     FILE_BUFFER *file_buffer;
62     char *pagename;
63{
64  NODE *node;
65
66  node = manpage_node_of_file_buffer (file_buffer, pagename);
67
68  if (!node)
69    {
70      char *page;
71
72      page = get_manpage_contents (pagename);
73
74      if (page)
75        {
76          char header[1024];
77          long oldsize, newsize;
78          int hlen, plen;
79
80          sprintf (header, "\n\n%c\n%s %s,  %s %s,  %s (dir)\n\n",
81                   INFO_COOKIE,
82                   INFO_FILE_LABEL, file_buffer->filename,
83                   INFO_NODE_LABEL, pagename,
84                   INFO_UP_LABEL);
85          oldsize = file_buffer->filesize;
86          hlen = strlen (header);
87          plen = strlen (page);
88          newsize = (oldsize + hlen + plen);
89          file_buffer->contents =
90            (char *)xrealloc (file_buffer->contents, 1 + newsize);
91          memcpy (file_buffer->contents + oldsize, header, hlen);
92          oldsize += hlen;
93          memcpy (file_buffer->contents + oldsize, page, plen);
94          file_buffer->contents[newsize] = '\0';
95          file_buffer->filesize = newsize;
96          file_buffer->finfo.st_size = newsize;
97          build_tags_and_nodes (file_buffer);
98          free (page);
99        }
100
101      node = manpage_node_of_file_buffer (file_buffer, pagename);
102    }
103
104  return (node);
105}
106
107FILE_BUFFER *
108create_manpage_file_buffer ()
109{
110  FILE_BUFFER *file_buffer = make_file_buffer ();
111  file_buffer->filename = xstrdup (MANPAGE_FILE_BUFFER_NAME);
112  file_buffer->fullpath = xstrdup (MANPAGE_FILE_BUFFER_NAME);
113  file_buffer->finfo.st_size = 0;
114  file_buffer->filesize = 0;
115  file_buffer->contents = (char *)NULL;
116  file_buffer->flags = (N_IsInternal | N_CannotGC | N_IsManPage);
117
118  return (file_buffer);
119}
120
121/* Scan the list of directories in PATH looking for FILENAME.  If we find
122   one that is an executable file, return it as a new string.  Otherwise,
123   return a NULL pointer. */
124static char *
125executable_file_in_path (filename, path)
126     char *filename, *path;
127{
128  struct stat finfo;
129  char *temp_dirname;
130  int statable, dirname_index;
131
132  dirname_index = 0;
133
134  while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
135    {
136      char *temp;
137
138      /* Expand a leading tilde if one is present. */
139      if (*temp_dirname == '~')
140        {
141          char *expanded_dirname;
142
143          expanded_dirname = tilde_expand_word (temp_dirname);
144          free (temp_dirname);
145          temp_dirname = expanded_dirname;
146        }
147
148      temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
149      strcpy (temp, temp_dirname);
150      if (temp[(strlen (temp)) - 1] != '/')
151        strcat (temp, "/");
152      strcat (temp, filename);
153
154      free (temp_dirname);
155
156      statable = (stat (temp, &finfo) == 0);
157
158      /* If we have found a regular executable file, then use it. */
159      if ((statable) && (S_ISREG (finfo.st_mode)) &&
160          (access (temp, X_OK) == 0))
161        return (temp);
162      else
163        free (temp);
164    }
165  return ((char *)NULL);
166}
167
168/* Return the full pathname of the system man page formatter. */
169static char *
170find_man_formatter ()
171{
172  return (executable_file_in_path ("man", (char *)getenv ("PATH")));
173}
174
175static char *manpage_pagename = (char *)NULL;
176static char *manpage_section  = (char *)NULL;
177
178static void
179get_page_and_section (pagename)
180     char *pagename;
181{
182  register int i;
183
184  if (manpage_pagename)
185    free (manpage_pagename);
186
187  if (manpage_section)
188    free (manpage_section);
189
190  manpage_pagename = (char *)NULL;
191  manpage_section  = (char *)NULL;
192
193  for (i = 0; pagename[i] != '\0' && pagename[i] != '('; i++);
194
195  manpage_pagename = (char *)xmalloc (1 + i);
196  strncpy (manpage_pagename, pagename, i);
197  manpage_pagename[i] = '\0';
198
199  if (pagename[i] == '(')
200    {
201      int start;
202
203      start = i + 1;
204
205      for (i = start; pagename[i] != '\0' && pagename[i] != ')'; i++);
206
207      manpage_section = (char *)xmalloc (1 + (i - start));
208      strncpy (manpage_section, pagename + start, (i - start));
209      manpage_section[i - start] = '\0';
210    }
211}
212
213static void
214reap_children (sig)
215     int sig;
216{
217  int status;
218  wait (&status);
219}
220
221static char *
222get_manpage_contents (pagename)
223     char *pagename;
224{
225  static char *formatter_args[4] = { (char *)NULL };
226  int pipes[2];
227  pid_t child;
228  char *formatted_page = (char *)NULL;
229  int arg_index = 1;
230
231  if (formatter_args[0] == (char *)NULL)
232    formatter_args[0] = find_man_formatter ();
233
234  if (formatter_args[0] == (char *)NULL)
235    return ((char *)NULL);
236
237  get_page_and_section (pagename);
238
239  if (manpage_section != (char *)NULL)
240    formatter_args[arg_index++] = manpage_section;
241
242  formatter_args[arg_index++] = manpage_pagename;
243  formatter_args[arg_index] = (char *)NULL;
244
245  /* Open a pipe to this program, read the output, and save it away
246     in FORMATTED_PAGE.  The reader end of the pipe is pipes[0]; the
247     writer end is pipes[1]. */
248  pipe (pipes);
249
250  signal (SIGCHLD, reap_children);
251
252  child = fork ();
253
254  if (child == -1)
255    return ((char *)NULL);
256
257  if (child != 0)
258    {
259      /* In the parent, close the writing end of the pipe, and read from
260         the exec'd child. */
261      close (pipes[1]);
262      formatted_page = read_from_fd (pipes[0]);
263      close (pipes[0]);
264    }
265  else
266    {
267      /* In the child, close the read end of the pipe, make the write end
268         of the pipe be stdout, and execute the man page formatter. */
269      close (pipes[0]);
270      close (fileno (stderr));
271      close (fileno (stdin));   /* Don't print errors. */
272      dup2 (pipes[1], fileno (stdout));
273
274      execv (formatter_args[0], formatter_args);
275
276      /* If we get here, we couldn't exec, so close out the pipe and
277         exit. */
278      close (pipes[1]);
279      exit (0);
280    }
281
282  /* If we have the page, then clean it up. */
283  if (formatted_page)
284    clean_manpage (formatted_page);
285
286  return (formatted_page);
287}
288
289static void
290clean_manpage (manpage)
291     char *manpage;
292{
293  register int i, j;
294  int newline_count = 0;
295  char *newpage;
296
297  newpage = (char *)xmalloc (1 + strlen (manpage));
298
299  for (i = 0, j = 0; (newpage[j] = manpage[i]); i++, j++)
300    {
301      if (manpage[i] == '\n')
302        newline_count++;
303      else
304        newline_count = 0;
305
306      if (newline_count == 3)
307        {
308          j--;
309          newline_count--;
310        }
311
312      if (manpage[i] == '\b' || manpage[i] == '\f')
313        j -= 2;
314    }
315
316  newpage[j++] = '\0';
317
318  strcpy (manpage, newpage);
319  free (newpage);
320}
321
322static NODE *
323manpage_node_of_file_buffer (file_buffer, pagename)
324     FILE_BUFFER *file_buffer;
325     char *pagename;
326{
327  NODE *node = (NODE *)NULL;
328  TAG *tag = (TAG *)NULL;
329
330  if (file_buffer->contents)
331    {
332      register int i;
333
334      for (i = 0; (tag = file_buffer->tags[i]); i++)
335        {
336          if (strcasecmp (pagename, tag->nodename) == 0)
337            break;
338        }
339    }
340
341  if (tag)
342    {
343      node = (NODE *)xmalloc (sizeof (NODE));
344      node->filename = file_buffer->filename;
345      node->nodename = tag->nodename;
346      node->contents = file_buffer->contents + tag->nodestart;
347      node->nodelen = tag->nodelen;
348      node->flags    = 0;
349      node->parent   = (char *)NULL;
350      node->flags = (N_HasTagsTable | N_IsManPage);
351      node->contents += skip_node_separator (node->contents);
352    }
353
354  return (node);
355}
356
357static char *
358read_from_fd (fd)
359     int fd;
360{
361  struct timeval timeout;
362  char *buffer = (char *)NULL;
363  int bsize = 0;
364  int bindex = 0;
365  int select_result;
366#if defined (FD_SET)
367  fd_set read_fds;
368
369  timeout.tv_sec = 15;
370  timeout.tv_usec = 0;
371
372  FD_ZERO (&read_fds);
373  FD_SET (fd, &read_fds);
374
375  select_result = select (fd + 1, fd_set_cast (&read_fds), 0, 0, &timeout);
376#else /* !FD_SET */
377  select_result = 1;
378#endif /* !FD_SET */
379
380  switch (select_result)
381    {
382    case 0:
383    case -1:
384      break;
385
386    default:
387      {
388        int amount_read;
389        int done = 0;
390
391        while (!done)
392          {
393            while ((bindex + 1024) > (bsize))
394              buffer = (char *)xrealloc (buffer, (bsize += 1024));
395            buffer[bindex] = '\0';
396
397            amount_read = read (fd, buffer + bindex, 1023);
398
399            if (amount_read < 0)
400              {
401                done = 1;
402              }
403            else
404              {
405                bindex += amount_read;
406                buffer[bindex] = '\0';
407                if (amount_read == 0)
408                  done = 1;
409              }
410          }
411      }
412    }
413
414  if ((buffer != (char *)NULL) && (*buffer == '\0'))
415    {
416      free (buffer);
417      buffer = (char *)NULL;
418    }
419
420  return (buffer);
421}
422
423static char *reference_section_starters[] =
424{
425  "\nRELATED INFORMATION",
426  "\nRELATED\tINFORMATION",
427  "RELATED INFORMATION\n",
428  "RELATED\tINFORMATION\n",
429  "\nSEE ALSO",
430  "\nSEE\tALSO",
431  "SEE ALSO\n",
432  "SEE\tALSO\n",
433  (char *)NULL
434};
435
436static SEARCH_BINDING frs_binding;
437
438static SEARCH_BINDING *
439find_reference_section (node)
440     NODE *node;
441{
442  register int i;
443  long position = -1;
444
445  frs_binding.buffer = node->contents;
446  frs_binding.start = 0;
447  frs_binding.end = node->nodelen;
448  frs_binding.flags = S_SkipDest;
449
450  for (i = 0; reference_section_starters[i] != (char *)NULL; i++)
451    {
452      position = search_forward (reference_section_starters[i], &frs_binding);
453      if (position != -1)
454        break;
455    }
456
457  if (position == -1)
458    return ((SEARCH_BINDING *)NULL);
459
460  /* We found the start of the reference section, and point is right after
461     the string which starts it.  The text from here to the next header
462     (or end of buffer) contains the only references in this manpage. */
463  frs_binding.start = position;
464
465  for (i = frs_binding.start; i < frs_binding.end - 2; i++)
466    {
467      if ((frs_binding.buffer[i] == '\n') &&
468          (!whitespace (frs_binding.buffer[i + 1])))
469        {
470          frs_binding.end = i;
471          break;
472        }
473    }
474
475  return (&frs_binding);
476}
477
478REFERENCE **
479xrefs_of_manpage (node)
480     NODE *node;
481{
482  SEARCH_BINDING *reference_section;
483  REFERENCE **refs = (REFERENCE **)NULL;
484  int refs_index = 0;
485  int refs_slots = 0;
486  long position;
487
488  reference_section = find_reference_section (node);
489
490  if (reference_section == (SEARCH_BINDING *)NULL)
491    return ((REFERENCE **)NULL);
492
493  /* Grovel the reference section building a list of references found there.
494     A reference is alphabetic characters followed by non-whitespace text
495     within parenthesis. */
496  reference_section->flags = 0;
497
498  while ((position = search_forward ("(", reference_section)) != -1)
499    {
500      register int start, end;
501
502      for (start = position; start > reference_section->start; start--)
503        if (whitespace (reference_section->buffer[start]))
504          break;
505
506      start++;
507
508      for (end = position; end < reference_section->end; end++)
509        {
510          if (whitespace (reference_section->buffer[end]))
511            {
512              end = start;
513              break;
514            }
515
516          if (reference_section->buffer[end] == ')')
517            {
518              end++;
519              break;
520            }
521        }
522
523      if (end != start)
524        {
525          REFERENCE *entry;
526          int len = end - start;
527
528          entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
529          entry->label = (char *)xmalloc (1 + len);
530          strncpy (entry->label, (reference_section->buffer) + start, len);
531          entry->label[len] = '\0';
532          entry->filename = xstrdup (node->filename);
533          entry->nodename = xstrdup (entry->label);
534          entry->start = start;
535          entry->end = end;
536
537          add_pointer_to_array
538            (entry, refs_index, refs, refs_slots, 10, REFERENCE *);
539        }
540
541      reference_section->start = position + 1;
542    }
543
544  return (refs);
545}
546
547long
548locate_manpage_xref (node, start, dir)
549     NODE *node;
550     long start;
551     int dir;
552{
553  REFERENCE **refs;
554  long position = -1;
555
556  refs = xrefs_of_manpage (node);
557
558  if (refs)
559    {
560      register int i, count;
561      REFERENCE *entry;
562
563      for (i = 0; refs[i]; i++);
564      count = i;
565
566      if (dir > 0)
567        {
568          for (i = 0; (entry = refs[i]); i++)
569            if (entry->start > start)
570              {
571                position = entry->start;
572                break;
573              }
574        }
575      else
576        {
577          for (i = count - 1; i > -1; i--)
578            {
579              entry = refs[i];
580
581              if (entry->start < start)
582                {
583                  position = entry->start;
584                  break;
585                }
586            }
587        }
588
589      info_free_references (refs);
590    }
591  return (position);
592}
593
594/* This one was a little tricky.  The binding buffer that is passed in has
595   a START and END value of 0 -- strlen (window-line-containing-point).
596   The BUFFER is a pointer to the start of that line. */
597REFERENCE **
598manpage_xrefs_in_binding (node, binding)
599     NODE *node;
600     SEARCH_BINDING *binding;
601{
602  register int i;
603  REFERENCE **all_refs = xrefs_of_manpage (node);
604  REFERENCE **brefs = (REFERENCE **)NULL;
605  REFERENCE *entry;
606  int brefs_index = 0;
607  int brefs_slots = 0;
608  int start, end;
609
610  if (!all_refs)
611    return ((REFERENCE **)NULL);
612
613  start = binding->start + (binding->buffer - node->contents);
614  end = binding->end + (binding->buffer - node->contents);
615
616  for (i = 0; (entry = all_refs[i]); i++)
617    {
618      if ((entry->start > start) && (entry->end < end))
619        {
620          add_pointer_to_array
621            (entry, brefs_index, brefs, brefs_slots, 10, REFERENCE *);
622        }
623      else
624        {
625          maybe_free (entry->label);
626          maybe_free (entry->filename);
627          maybe_free (entry->nodename);
628          free (entry);
629        }
630    }
631
632  free (all_refs);
633  return (brefs);
634}
635