1/*
2 * rarian-man.c
3 * This file is part of Rarian
4 *
5 * Copyright (C) 2007 - Don Scorgie
6 *
7 * Rarian is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Rarian is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22#include <config.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <unistd.h>
26#include <dirent.h>
27#include <string.h>
28#include <sys/stat.h>
29#include "rarian-man.h"
30#include "rarian-language.h"
31#include "rarian-utils.h"
32
33/*#ifdef HAVE_LIBGDBM
34#include <gdbm.h>
35static GDBM_FILE *gdbm_handle;
36#endif
37
38#ifdef HAVE_LIBNDBM
39#include <ndbm.h>
40static DBM *ndbm_handle;
41#endif*/
42
43typedef struct _ManLink ManLink;
44
45struct _ManLink
46{
47  RrnManEntry *reg;
48  ManLink *next;
49  ManLink *prev;
50};
51
52static char *keys[43] = {"1", "1p", "1g", "1t", "1x", "1ssl", "1m",
53    "2",
54    "3", "3o", "3t", "3p", "3blt", "3nas", "3form", "3menu", "3tiff", "3ssl", "3readline",
55    "3ncurses", "3curses", "3f", "3pm", "3perl", "3qt", "3x", "3X11",
56    "4", "4x",
57    "5", "5snmp", "5x", "5ssl",
58    "6", "6x",
59    "7", "7gcc", "7x", "7ssl",
60    "8", "8l", "9", "0p"
61};
62
63static ManLink *manhead[44];
64static ManLink *mantail[44];
65
66static char *avail_dirs[] = {
67    "man0p", "man1", "man1p", "man2",  "man3",
68    "man3p", "man4", "man5", "man6", "man7",
69    "man8", "man9", "mann",
70    NULL
71    };
72
73static char **man_paths = NULL;
74
75int initialised = FALSE;
76int mode = -1;
77/* Modes: 0 = default
78 *        1 = gdbm
79 *        2 = ndbm
80 */
81
82static void
83setup_man_path ()
84{
85  int outfd[2];
86  int infd[2];
87
88  int oldstdin, oldstdout;
89  fflush(stdin);
90  fflush(stdout);
91  fflush(stderr);
92
93  pipe(outfd); // Where the parent is going to write to
94  pipe(infd); // From where parent is going to read
95
96  oldstdin = dup(0); // Save current stdin
97  oldstdout = dup(1); // Save stdout
98
99  close(0);
100  close(1);
101
102  dup2(outfd[0], 0); // Make the read end of outfd pipe as stdin
103  dup2(infd[1],1); // Make the write end of infd as stdout
104
105  if(!fork()) {
106    /* Child process */
107    char *argv[]={"manpath"};
108    close(outfd[0]); // Not required for the child
109    close(outfd[1]);
110    close(infd[0]);
111    close(infd[1]);
112    execlp("manpath", "manpath", (char *) 0);
113    exit (0);
114  } else {
115    /* Parent process */
116    char *input = NULL;
117    char *colon = NULL;
118    char *next = NULL;
119    int i, count = 0;
120    input = malloc(sizeof(char) * 256);
121    close(0); // Restore the original std fds of parent
122    close(1);
123    dup2(oldstdin, 0);
124    dup2(oldstdout, 1);
125
126    close(outfd[0]); // These are being used by the child
127    close(infd[1]);
128    memset(input, 0, sizeof(char)*255);
129
130    input[read(infd[0],input,255)] = 0; // Read from child's stdout
131    if (*input != '\0') {
132      int i;
133      i = strlen(input);
134      input[i-1]='\0';
135    }
136    if (!input || *input == '\0') {
137      char *env = NULL;
138      env = getenv("MANPATH");
139      if (env)
140	input = strdup(env);
141    }
142    if (!input || *input == '\0') {
143      if (input)
144	free(input);
145      input = strdup ("@DEFAULT_MANPATH@");
146    }
147    colon = input;
148
149    while (*colon) {
150      if (*colon == ':')
151	count++;
152      colon++;
153    }
154    man_paths = malloc (sizeof(char *) * (count+2)); /* 2 is for final string
155						      * + NULL entry */
156    colon = input;
157    for (i=0; i< count; i++) {
158      next = strchr (colon, ':');
159      man_paths[i] = rrn_strndup(colon, next-colon);
160      colon = next;
161      colon++;
162    }
163    /* Final 2 entries - straight strdup of final entry and NULL */
164    man_paths[i] = strdup(colon);
165    i++;
166    man_paths[i] = NULL;
167    free (input);
168  }
169
170}
171#if 0
172#ifdef HAVE_LIBGDBM
173static void
174setup_gdbm (char *name)
175{
176  gdbm_handle = gdbm_open(name, 0, GDBM_READER, 0666, 0);
177  if (!gdbm_handle) {
178    sprintf(stderr, "ERROR: GDBM index %s could not be opened.  Falling back\n", name);
179    setup_default();
180  } else {
181    initialised = TRUE;
182    mode = 1;
183  }
184}
185#endif
186
187#ifdef HAVE_LIBNDBM
188static void
189setup_ndbm (char *name)
190{
191  char *trunc;
192
193  trunc = malloc (sizeof(char) * (strlen(name)-3));
194  trunc = rrn_strndup (name, strlen(name) - 4);
195  ndbm_handle = dbm_open(trunc, O_RDONLY, 0666);
196  if (!ndbm_handle) {
197    sprintf(stderr, "ERROR: NDBM index %s could not be opened.  Falling back\n", trunc);
198    setup_default();
199  } else {
200    initialise = TRUE;
201    mode = 2;
202  }
203  free (trunc);
204}
205#endif
206#endif
207
208static char *strrstr (char *s, char *wanted)
209{
210    char *scan;
211    char *first;
212    int len;
213
214    first = wanted;
215    len = strlen(wanted);
216    for (scan = s + strlen(s) - len ; scan >= s ; scan--)
217    {
218        if (*scan == *first)
219        {
220            if (!strncmp (scan, wanted, len))
221            {
222                return (scan);
223            }
224        }
225    }
226    return(NULL);
227}
228
229
230
231static char *
232get_name_for_file (char *filename, char **subsect)
233{
234  char *suffix;
235  char *cut;
236  char *sect;
237  char *final;
238
239  /* We assume, like reasonable people, that man pages
240   * have one of the forms:
241   * manname.sect.{gz,bz,bz2,lzma}
242   * manname.sect
243   * If it doesn't, things will probably break but we return
244   * our "best guess" (i.e. everything up to the suffix)
245   */
246  suffix = strrstr(filename, ".gz");
247  if (!suffix) {
248    suffix = strrstr(filename, ".bz2");
249    if (!suffix) {
250      suffix = strrstr(filename, ".bz");
251    }
252      if (!suffix) {
253        suffix = strrstr(filename, ".lzma");
254      }
255  }
256  if (suffix)
257    cut = rrn_strndup (filename, suffix-filename);
258  else
259    cut = strdup (filename);
260  sect = strrchr (cut, '.');
261  if (!sect)
262    return cut;
263
264  final = rrn_strndup (cut, sect-cut);
265  sect++;
266  *subsect = strdup (sect);
267  free (cut);
268  return final;
269
270}
271
272static int
273check_for_dup (RrnManEntry *reg, int entry)
274{
275  ManLink *iter = manhead[entry];
276
277  while (iter) {
278    if (!strcmp(reg->name, iter->reg->name))
279      return TRUE;
280    iter = iter->next;
281
282  }
283  return FALSE;
284}
285
286static int
287find_key (char *sect)
288{
289  int i;
290  for (i=0; i<43; i++){
291    if (!strcmp (sect, keys[i]))
292      return i;
293  }
294  return i;
295}
296
297static void
298process_dir(char *dir)
299{
300  char **dir_iter;
301  char *path = NULL;
302
303  DIR * dirp = NULL;
304  struct dirent * dp = NULL;
305  struct stat buf;
306  int current;
307
308  current = -1;
309  path = malloc(sizeof(char) * (strlen(dir) + 8));
310
311  dir_iter = avail_dirs;
312  while (dir_iter && *dir_iter) {
313    current++;
314    sprintf(path, "%s/%s", dir, *dir_iter);
315    if (access (path, R_OK)) {
316      dir_iter++;
317      continue;
318    }
319
320    dirp = opendir (path);
321
322    if (!dirp) {
323      dir_iter++;
324      continue;
325    }
326
327    while (1) {
328      if ((dp = readdir(dirp)) != NULL) {
329	char *full_name;
330
331	full_name = malloc (sizeof(char) * (strlen(dp->d_name) + strlen (path) + 3));
332	sprintf (full_name, "%s/%s", path, dp->d_name);
333
334	stat(full_name,&buf);
335	if (S_ISREG(buf.st_mode) || buf.st_mode & S_IFLNK) {
336	  char *tmp = NULL;
337	  char *suffix = NULL;
338	  RrnManEntry *entry;
339	  ManLink *link;
340	  char *subsect = NULL;
341
342	  entry = malloc (sizeof(RrnManEntry));
343	  entry->name = get_name_for_file (dp->d_name, &subsect);
344	  entry->path = full_name;
345	  if (subsect) {
346	    entry->section = subsect;
347	    entry->comment = NULL;
348	    current = find_key(subsect);
349	    if (!check_for_dup (entry, current)) {
350	      link = malloc (sizeof(ManLink));
351	      link->reg = entry;
352
353	      if (mantail[current]) {
354		mantail[current]->next = link;
355		link->next = NULL;
356		link->prev = mantail[current];
357		mantail[current] = link;
358	      } else {
359		manhead[current] = mantail[current] = link;
360		link->prev = link->next = NULL;
361	      }
362	    } else {
363	      free (entry->name);
364	      free (entry->path);
365	      free (entry->section);
366	      if (entry->comment)
367		free (entry->comment);
368	      free (entry);
369	    }
370	  }
371	}
372      } else {
373	closedir (dirp);
374	break;
375      }
376    }
377    dir_iter++;
378  }
379  free (path);
380}
381
382static void
383setup_default()
384{
385  char **path_iter;
386  char **langs;
387  char **lang_iter;
388
389  path_iter = man_paths;
390  langs = rrn_language_get_langs();
391
392  while (path_iter && *path_iter) {
393    if (access (*path_iter, R_OK)) {
394      path_iter++;
395      continue;
396    }
397
398    lang_iter = langs;
399    while (lang_iter && *lang_iter) {
400      char *path;
401      path = malloc (sizeof(char) * (strlen(*path_iter) +
402				     strlen(*lang_iter)+2));
403      sprintf (path, "%s/%s", *path_iter, *lang_iter);
404      if (!access (path, R_OK)) {
405	process_dir(path);
406      }
407      free (path);
408      lang_iter++;
409    }
410
411
412    process_dir(*path_iter);
413    path_iter++;
414  }
415
416  /*lang_iter = langs;
417  while (lang_iter && *lang_iter) {
418    char **next = lang_iter;
419    next++;
420    free (lang_iter);
421    lang_iter = next;
422    }*/
423  free (langs);
424}
425
426static void
427rrn_man_init (void)
428{
429  char *default_dirs = "/var/cache/man:/usr/man";
430  char *split = NULL;
431#if 0
432  char *gdbm_index = "index.db";
433  char *ndbm_index = "index.dir";
434#endif
435  int i;
436  int npaths = 0;
437  char *ddirs;
438
439  for (i=0; i<44; i++) {
440    manhead[i] = mantail[i] = NULL;
441  }
442
443  setup_man_path ();
444
445  split = default_dirs;
446
447#if 0
448  while (split) {
449    char *next = strchr(split, ':');
450    char *dirname = NULL;
451    char *index = NULL;
452    FILE *access = NULL;
453
454    if (next)
455      dirname = rrn_strndup(split, (next-split));
456    else
457      dirname = strdup (split);
458#ifdef HAVE_LIBGDBM
459    index = malloc (sizeof(char) * (strlen(dirname) + 10 /*Len of gdbm_index + additional chars*/));
460    sprintf (index, "%s/%s", dirname, gdbm_index);
461    access = fopen(index, "r");
462    free(dirname);
463    if (access) {
464      fclose(access);
465      setup_gdbm(index);
466      free(index);
467      return;
468    }
469#endif
470#ifdef HAVE_NDBM
471    index = malloc (sizeof(char) * (strlen(dirname) + 11 /*Len of ndbm_index + additional chars*/));
472    sprintf (index, "%s/%s", dirname, gdbm_index);
473    access = fopen(index, "r");
474    free(dirname);
475    if (access) {
476      fclose(access);
477      setup_ndbm(index);
478      free(index);
479      return;
480    }
481#endif
482    split = strchr(split, ':');
483    if (split)
484      split++;
485  }
486#endif /* 0 */
487  /* If we get here, we have to do our own thang */
488  setup_default();
489  initialised = TRUE;
490
491}
492
493void
494rrn_man_for_each (RrnManForeachFunc funct, void * user_data)
495{
496  ManLink *iter;
497  int i;
498
499  if (!initialised)
500    rrn_man_init();
501
502  for (i=0; i<44; i++) {
503    iter = manhead[i];
504
505    while (iter) {
506      int res;
507      res = funct (iter->reg, user_data);
508      if (res == FALSE)
509	break;
510      iter = iter->next;
511    }
512  }
513  return;
514}
515
516
517void
518rrn_man_for_each_in_category (char *category,
519				RrnManForeachFunc funct,
520				void * user_data)
521{
522  ManLink *iter;
523  int cat;
524
525  if (!initialised)
526    rrn_man_init();
527
528  cat = find_key(category);
529  iter = manhead[cat];
530
531  while (iter) {
532    int res;
533    if (!strcmp (iter->reg->section, category)) {
534      res = funct (iter->reg, user_data);
535      if (res == FALSE)
536	break;
537    }
538    iter = iter->next;
539  }
540  return;
541}
542
543RrnManEntry *
544rrn_man_find_from_name (char *name, char *sect)
545{
546  ManLink *iter;
547  int cat;
548  RrnManEntry *res;
549
550  if (!initialised)
551    rrn_man_init ();
552
553  if (sect) {
554    cat = find_key(sect);
555  } else {
556    int i=0;
557    for (i=0; i<43; i++) {
558      iter = manhead[i];
559
560      while (iter) {
561	if (!strcmp (iter->reg->name, name)) {
562	  return iter->reg;
563	}
564	iter = iter->next;
565      }
566    }
567    return NULL;
568  }
569  iter = manhead[cat];
570
571  while (iter) {
572    if (!strcmp (iter->reg->name, name)) {
573      return iter->reg;
574    }
575    iter = iter->next;
576  }
577
578
579  return NULL;
580}
581
582char **
583rrn_man_get_categories (void)
584{
585  if (!initialised)
586    rrn_man_init();
587
588  return keys;
589}
590
591
592void
593rrn_man_shutdown ()
594{
595  ManLink *iter;
596  int i;
597  initialised = FALSE;
598
599  for (i=0; i< 44; i++) {
600    iter = manhead[i];
601    while (iter) {
602      ManLink *tmp = iter->next;
603      free (iter->reg->name);
604      free (iter->reg->path);
605      free (iter->reg->section);
606      if (iter->reg->comment)
607	free (iter->reg->comment);
608      free (iter->reg);
609      free (iter);
610      iter = tmp;
611    }
612    manhead[i] = mantail[i] = NULL;
613  }
614  rrn_language_shutdown ();
615  return;
616}
617