PennMUSH Community

root/1.8.3/trunk/src/help.c

Revision 1191, 15.3 kB (checked in by shawnw, 8 months ago)

Fixed rare double free() in help file reindexing

Line 
1 /**
2  * \file help.c
3  *
4  * \brief The PennMUSH help system.
5  *
6  *
7  */
8 #include "config.h"
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <stdio.h>
13 #include "conf.h"
14 #include "externs.h"
15 #include "command.h"
16 #include "htab.h"
17 #include "help.h"
18 #include "log.h"
19 #include "ansi.h"
20 #include "parse.h"
21 #include "pueblo.h"
22 #include "flags.h"
23 #include "dbdefs.h"
24 #include "mymalloc.h"
25 #include "confmagic.h"
26
27 HASHTAB help_files;  /**< Help filenames hash table */
28
29 static int help_init = 0;
30
31 static void do_new_spitfile(dbref player, char *arg1, help_file *help_dat);
32 static const char *string_spitfile(help_file *help_dat, char *arg1);
33 static help_indx *help_find_entry(help_file *help_dat, const char *the_topic);
34 static char **list_matching_entries(const char *pattern,
35                                     help_file *help_dat, int *len);
36 static void free_entry_list(char **);
37 static const char *normalize_entry(help_file *help_dat, const char *arg1);
38
39 static void help_build_index(help_file *h, int restricted);
40
41 /** Linked list of help topic names. */
42 typedef struct TLIST {
43   char topic[TOPIC_NAME_LEN + 1];       /**< Name of topic */
44   struct TLIST *next;                   /**< Pointer to next list entry */
45 } tlist;
46
47 tlist *top = NULL;   /**< Pointer to top of linked list of topic names */
48
49 help_indx *topics = NULL;  /**< Pointer to linked list of topic indexes */
50 unsigned num_topics = 0;   /**< Number of topics loaded */
51 unsigned top_topics = 0;   /**< Maximum number of topics loaded */
52
53 static void write_topic(long int p);
54
55 COMMAND(cmd_helpcmd)
56 {
57   help_file *h;
58
59   h = hashfind(cmd->name, &help_files);
60
61   if (!h) {
62     notify(player, T("That command is unavailable."));
63     return;
64   }
65
66   if (h->admin && !Hasprivs(player)) {
67     notify(player, T("You don't look like an admin to me."));
68     return;
69   }
70
71   if (wildcard(arg_left)) {
72     int len = 0;
73     char **entries;
74
75     entries = list_matching_entries(arg_left, h, &len);
76     if (len == 0)
77       notify_format(player, T("No entries matching '%s' were found."),
78                     arg_left);
79     else if (len == 1)
80       do_new_spitfile(player, *entries, h);
81     else {
82       char buff[BUFFER_LEN];
83       char *bp;
84
85       bp = buff;
86       arr2list(entries, len, buff, &bp, ", ");
87       *bp = '\0';
88       notify_format(player, T("Here are the entries which match '%s':\n%s"),
89                     arg_left, buff);
90     }
91     free_entry_list(entries);
92   } else
93     do_new_spitfile(player, arg_left, h);
94 }
95
96 /** Initialize the helpfile hashtable, which contains the names of thes
97  * help files.
98  */
99 void
100 init_help_files(void)
101 {
102   hashinit(&help_files, 8);
103   help_init = 1;
104 }
105
106 /** Add new help command. This function is
107  * the basis for the help_command directive in mush.cnf. It creates
108  * a new help entry for the hash table, builds a help index,
109  * and adds the new command to the command table.
110  * \param command_name name of help command to add.
111  * \param filename name of the help file to use for this command.
112  * \param admin if 1, this command reads admin topics, rather than standard.
113  */
114 void
115 add_help_file(const char *command_name, const char *filename, int admin)
116 {
117   help_file *h;
118
119   if (help_init == 0)
120     init_help_files();
121
122   if (!command_name || !filename || !*command_name || !*filename)
123     return;
124
125   /* If there's already an entry for it, complain */
126   h = hashfind(strupper(command_name), &help_files);
127   if (h) {
128     do_rawlog(LT_ERR, T("Duplicate help_command %s ignored."), command_name);
129     return;
130   }
131
132   h = mush_malloc(sizeof *h, "help_file.entry");
133   h->command = mush_strdup(strupper(command_name), "help_file.command");
134   h->file = mush_strdup(filename, "help_file.filename");
135   h->entries = 0;
136   h->indx = NULL;
137   h->admin = admin;
138   help_build_index(h, h->admin);
139   if (!h->indx) {
140     mush_free(h->command, "help_file.command");
141     mush_free(h->file, "help_file.filename");
142     mush_free(h, "help_file.entry");
143     return;
144   }
145   (void) command_add(h->command, CMD_T_ANY | CMD_T_NOPARSE, NULL, 0, NULL,
146                      cmd_helpcmd);
147   hashadd(h->command, h, &help_files);
148 }
149
150 /** Rebuild a help file index.
151  * \verbatim
152  * This command implements @readcache.
153  * \endverbatim
154  * \param player the enactor.
155  */
156 void
157 help_reindex(dbref player)
158 {
159   help_file *curr;
160
161   for (curr = hash_firstentry(&help_files);
162        curr; curr = hash_nextentry(&help_files)) {
163     if (curr->indx) {
164       mush_free(curr->indx, "help_index");
165       curr->indx = NULL;
166       curr->entries = 0;
167     }
168     help_build_index(curr, curr->admin);
169   }
170   if (player != NOTHING) {
171     notify(player, T("Help files reindexed."));
172     do_rawlog(LT_WIZ, T("Help files reindexed by %s(#%d)"), Name(player),
173               player);
174   } else
175     do_rawlog(LT_WIZ, T("Help files reindexed."));
176 }
177
178 static void
179 do_new_spitfile(dbref player, char *arg1, help_file *help_dat)
180 {
181   help_indx *entry = NULL;
182   FILE *fp;
183   char *p, line[LINE_SIZE + 1];
184   char the_topic[LINE_SIZE + 2];
185   int default_topic = 0;
186   size_t n;
187
188   if (*arg1 == '\0') {
189     default_topic = 1;
190     arg1 = (char *) help_dat->command;
191   } else if (*arg1 == '&') {
192     notify(player, T("Help topics don't start with '&'."));
193     return;
194   }
195   if (strlen(arg1) > LINE_SIZE)
196     *(arg1 + LINE_SIZE) = '\0';
197
198   if (help_dat->admin) {
199     sprintf(the_topic, "&%s", arg1);
200   } else
201     strcpy(the_topic, arg1);
202
203   if (!help_dat->indx || help_dat->entries == 0) {
204     notify(player, T("Sorry, that command is temporarily unvailable."));
205     do_rawlog(LT_ERR, T("No index for %s."), help_dat->command);
206     return;
207   }
208
209   entry = help_find_entry(help_dat, the_topic);
210   if (!entry && default_topic)
211     entry = help_find_entry(help_dat, (help_dat->admin ? "&help" : "help"));
212
213   if (!entry) {
214     notify_format(player, T("No entry for '%s'."), arg1);
215     return;
216   }
217
218   if ((fp = fopen(help_dat->file, FOPEN_READ)) == NULL) {
219     notify(player, T("Sorry, that function is temporarily unavailable."));
220     do_log(LT_ERR, 0, 0, T("Can't open text file %s for reading"),
221            help_dat->file);
222     return;
223   }
224   if (fseek(fp, entry->pos, 0) < 0L) {
225     notify(player, T("Sorry, that function is temporarily unavailable."));
226     do_rawlog(LT_ERR, T("Seek error in file %s"), help_dat->file);
227     return;
228   }
229   strcpy(the_topic, strupper(entry->topic + (*entry->topic == '&')));
230   /* ANSI topics */
231   if (ShowAnsi(player)) {
232     char ansi_topic[LINE_SIZE + 10];
233     sprintf(ansi_topic, "%s%s%s", ANSI_HILITE, the_topic, ANSI_END);
234     notify(player, ansi_topic);
235   } else
236     notify(player, the_topic);
237
238   if (SUPPORT_PUEBLO)
239     notify_noenter(player, open_tag("SAMP"));
240   for (n = 0; n < BUFFER_LEN; n++) {
241     if (fgets(line, LINE_SIZE, fp) == NULL)
242       break;
243     if (line[0] == '&')
244       break;
245     if (line[0] == '\n') {
246       notify(player, " ");
247     } else {
248       for (p = line; *p != '\0'; p++)
249         if (*p == '\n')
250           *p = '\0';
251       notify(player, line);
252     }
253   }
254   if (SUPPORT_PUEBLO)
255     notify(player, close_tag("SAMP"));
256   fclose(fp);
257   if (n >= BUFFER_LEN)
258     notify_format(player, T("%s output truncated."), help_dat->command);
259 }
260
261
262 static help_indx *
263 help_find_entry(help_file *help_dat, const char *the_topic)
264 {
265   help_indx *entry = NULL;
266
267   if (help_dat->entries < 10) { /* Just do a linear search for small files */
268     size_t n;
269     for (n = 0; n < help_dat->entries; n++) {
270       if (string_prefix(help_dat->indx[n].topic, the_topic)) {
271         entry = &help_dat->indx[n];
272         break;
273       }
274     }
275   } else {                      /* Binary search of the index */
276     int left = 0;
277     int cmp;
278     int right = help_dat->entries - 1;
279
280     while (1) {
281       int n = (left + right) / 2;
282
283       if (left > right)
284         break;
285
286       cmp = strcasecmp(the_topic, help_dat->indx[n].topic);
287
288       if (cmp == 0) {
289         entry = &help_dat->indx[n];
290         break;
291       } else if (cmp < 0) {
292         /* We need to catch the first prefix */
293         if (string_prefix(help_dat->indx[n].topic, the_topic)) {
294           int m;
295           for (m = n - 1; m >= 0; m--) {
296             if (!string_prefix(help_dat->indx[m].topic, the_topic))
297               break;
298           }
299           entry = &help_dat->indx[m + 1];
300           break;
301         }
302         if (left == right)
303           break;
304         right = n - 1;
305       } else {                  /* cmp > 0 */
306         if (left == right)
307           break;
308         left = n + 1;
309       }
310     }
311   }
312   return entry;
313 }
314
315 static void
316 write_topic(long int p)
317 {
318   tlist *cur, *nextptr;
319   help_indx *temp;
320   for (cur = top; cur; cur = nextptr) {
321     nextptr = cur->next;
322     if (num_topics >= top_topics) {
323       top_topics += top_topics / 2 + 20;
324       if (topics)
325         topics = (help_indx *) realloc(topics, top_topics * sizeof(help_indx));
326       else
327         topics = (help_indx *) malloc(top_topics * sizeof(help_indx));
328       if (!topics) {
329         mush_panic(T("Out of memory"));
330       }
331     }
332     temp = &topics[num_topics++];
333     temp->pos = p;
334     strcpy(temp->topic, cur->topic);
335     free(cur);
336   }
337   top = NULL;
338 }
339
340 static int WIN32_CDECL topic_cmp(const void *s1, const void *s2);
341 static int WIN32_CDECL
342 topic_cmp(const void *s1, const void *s2)
343 {
344   const help_indx *a = s1;
345   const help_indx *b = s2;
346
347   return strcasecmp(a->topic, b->topic);
348
349 }
350
351 static void
352 help_build_index(help_file *h, int restricted)
353 {
354   long bigpos, pos = 0;
355   bool in_topic;
356   int i, lineno, ntopics;
357   size_t n;
358   char *s, *topic;
359   char the_topic[TOPIC_NAME_LEN + 1];
360   char line[LINE_SIZE + 1];
361   FILE *rfp;
362   tlist *cur;
363
364   /* Quietly ignore null values for the file */
365   if (!h || !h->file)
366     return;
367   if ((rfp = fopen(h->file, FOPEN_READ)) == NULL) {
368     do_rawlog(LT_ERR, T("Can't open %s for reading"), h->file);
369     return;
370   }
371
372   if (restricted)
373     do_rawlog(LT_WIZ, T("Indexing file %s (admin topics)"), h->file);
374   else
375     do_rawlog(LT_WIZ, T("Indexing file %s"), h->file);
376   topics = NULL;
377   num_topics = 0;
378   top_topics = 0;
379   bigpos = 0L;
380   lineno = 0;
381   ntopics = 0;
382
383   in_topic = 0;
384
385   while (fgets(line, LINE_SIZE, rfp) != NULL) {
386     ++lineno;
387     if (ntopics == 0) {
388       /* Looking for the first topic, but we'll ignore blank lines */
389       if (!line[0]) {
390         /* Someone's feeding us /dev/null? */
391         do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"),
392                   h->file);
393         fclose(rfp);
394         return;
395       }
396       if (isspace((unsigned char) line[0]))
397         continue;
398       if (line[0] != '&') {
399         do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"),
400                   h->file);
401         fclose(rfp);
402         return;
403       }
404     }
405     n = strlen(line);
406     if (line[n - 1] != '\n') {
407       do_rawlog(LT_ERR, T("Line %d of %s: line too long"), lineno, h->file);
408     }
409     if (line[0] == '&') {
410       ++ntopics;
411       if (!in_topic) {
412         /* Finish up last entry */
413         if (ntopics > 1) {
414           write_topic(pos);
415         }
416         in_topic = true;
417       }
418       /* parse out the topic */
419       /* Get the beginning of the topic string */
420       for (topic = &line[1];
421            (*topic == ' ' || *topic == '\t') && *topic != '\0'; topic++) ;
422
423       /* Get the topic */
424       strcpy(the_topic, "");
425       for (i = -1, s = topic; *s != '\n' && *s != '\0'; s++) {
426         if (i >= TOPIC_NAME_LEN - 1)
427           break;
428         if (*s != ' ' || the_topic[i] != ' ')
429           the_topic[++i] = *s;
430       }
431       if ((restricted && the_topic[0] == '&')
432           || (!restricted && the_topic[0] != '&')) {
433         the_topic[++i] = '\0';
434         cur = (tlist *) malloc(sizeof(tlist));
435         strcpy(cur->topic, the_topic);
436         cur->next = top;
437         top = cur;
438       }
439     } else {
440       if (in_topic) {
441         pos = bigpos;
442       }
443       in_topic = false;
444     }
445     bigpos = ftell(rfp);
446   }
447
448   /* Handle last topic */
449   write_topic(pos);
450   qsort(topics, num_topics, sizeof(help_indx), topic_cmp);
451   h->entries = num_topics;
452   h->indx = topics;
453   add_check("help_index");
454   fclose(rfp);
455   do_rawlog(LT_WIZ, T("%d topics indexed."), num_topics);
456   return;
457 }
458
459 /* ARGSUSED */
460 FUNCTION(fun_textfile)
461 {
462   help_file *h;
463
464   h = hashfind(strupper(args[0]), &help_files);
465   if (!h) {
466     safe_str(T("#-1 NO SUCH FILE"), buff, bp);
467     return;
468   }
469   if (h->admin && !Hasprivs(executor)) {
470     safe_str(T(e_perm), buff, bp);
471     return;
472   }
473
474   if (wildcard(args[1])) {
475     char **entries;
476     int len = 0;
477     entries = list_matching_entries(args[1], h, &len);
478     if (len == 0)
479       safe_str(T("No matching help topics."), buff, bp);
480     else
481       arr2list(entries, len, buff, bp, ", ");
482     free_entry_list(entries);
483   } else
484     safe_str(string_spitfile(h, args[1]), buff, bp);
485 }
486
487 /* ARGSUSED */
488 FUNCTION(fun_textentries)
489 {
490   help_file *h;
491   char **entries;
492   int len = 0;
493   const char *sep = " ";
494
495   h = hashfind(strupper(args[0]), &help_files);
496   if (!h) {
497     safe_str(T("#-1 NO SUCH FILE"), buff, bp);
498     return;
499   }
500   if (h->admin && !Hasprivs(executor)) {
501     safe_str