| 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 = (help_file *) hash_firstentry(&help_files); |
|---|
| 162 | curr; curr = (help_file *) hash_nextentry(&help_files)) { |
|---|
| 163 | if (curr->indx) { |
|---|
| 164 | mush_free((Malloc_t) curr->indx, "help_index"); |
|---|
| 165 | curr->entries = 0; |
|---|
| 166 | } |
|---|
| 167 | help_build_index(curr, curr->admin); |
|---|
| 168 | } |
|---|
| 169 | if (player != NOTHING) { |
|---|
| 170 | notify(player, T("Help files reindexed.")); |
|---|
| 171 | do_rawlog(LT_WIZ, T("Help files reindexed by %s(#%d)"), Name(player), |
|---|
| 172 | player); |
|---|
| 173 | } else |
|---|
| 174 | do_rawlog(LT_WIZ, T("Help files reindexed.")); |
|---|
| 175 | } |
|---|
| 176 | |
|---|
| 177 | static void |
|---|
| 178 | do_new_spitfile(dbref player, char *arg1, help_file *help_dat) |
|---|
| 179 | { |
|---|
| 180 | help_indx *entry = NULL; |
|---|
| 181 | FILE *fp; |
|---|
| 182 | char *p, line[LINE_SIZE + 1]; |
|---|
| 183 | char the_topic[LINE_SIZE + 2]; |
|---|
| 184 | int default_topic = 0; |
|---|
| 185 | size_t n; |
|---|
| 186 | |
|---|
| 187 | if (*arg1 == '\0') { |
|---|
| 188 | default_topic = 1; |
|---|
| 189 | arg1 = (char *) help_dat->command; |
|---|
| 190 | } else if (*arg1 == '&') { |
|---|
| 191 | notify(player, T("Help topics don't start with '&'.")); |
|---|
| 192 | return; |
|---|
| 193 | } |
|---|
| 194 | if (strlen(arg1) > LINE_SIZE) |
|---|
| 195 | *(arg1 + LINE_SIZE) = '\0'; |
|---|
| 196 | |
|---|
| 197 | if (help_dat->admin) { |
|---|
| 198 | sprintf(the_topic, "&%s", arg1); |
|---|
| 199 | } else |
|---|
| 200 | strcpy(the_topic, arg1); |
|---|
| 201 | |
|---|
| 202 | if (!help_dat->indx || help_dat->entries == 0) { |
|---|
| 203 | notify(player, T("Sorry, that command is temporarily unvailable.")); |
|---|
| 204 | do_rawlog(LT_ERR, T("No index for %s."), help_dat->command); |
|---|
| 205 | return; |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | entry = help_find_entry(help_dat, the_topic); |
|---|
| 209 | if (!entry && default_topic) |
|---|
| 210 | entry = help_find_entry(help_dat, (help_dat->admin ? "&help" : "help")); |
|---|
| 211 | |
|---|
| 212 | if (!entry) { |
|---|
| 213 | notify_format(player, T("No entry for '%s'."), arg1); |
|---|
| 214 | return; |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | if ((fp = fopen(help_dat->file, FOPEN_READ)) == NULL) { |
|---|
| 218 | notify(player, T("Sorry, that function is temporarily unavailable.")); |
|---|
| 219 | do_log(LT_ERR, 0, 0, T("Can't open text file %s for reading"), |
|---|
| 220 | help_dat->file); |
|---|
| 221 | return; |
|---|
| 222 | } |
|---|
| 223 | if (fseek(fp, entry->pos, 0) < 0L) { |
|---|
| 224 | notify(player, T("Sorry, that function is temporarily unavailable.")); |
|---|
| 225 | do_rawlog(LT_ERR, T("Seek error in file %s"), help_dat->file); |
|---|
| 226 | return; |
|---|
| 227 | } |
|---|
| 228 | strcpy(the_topic, strupper(entry->topic + (*entry->topic == '&'))); |
|---|
| 229 | /* ANSI topics */ |
|---|
| 230 | if (ShowAnsi(player)) { |
|---|
| 231 | char ansi_topic[LINE_SIZE + 10]; |
|---|
| 232 | sprintf(ansi_topic, "%s%s%s", ANSI_HILITE, the_topic, ANSI_END); |
|---|
| 233 | notify(player, ansi_topic); |
|---|
| 234 | } else |
|---|
| 235 | notify(player, the_topic); |
|---|
| 236 | |
|---|
| 237 | if (SUPPORT_PUEBLO) |
|---|
| 238 | notify_noenter(player, open_tag("SAMP")); |
|---|
| 239 | for (n = 0; n < BUFFER_LEN; n++) { |
|---|
| 240 | if (fgets(line, LINE_SIZE, fp) == NULL) |
|---|
| 241 | break; |
|---|
| 242 | if (line[0] == '&') |
|---|
| 243 | break; |
|---|
| 244 | if (line[0] == '\n') { |
|---|
| 245 | notify(player, " "); |
|---|
| 246 | } else { |
|---|
| 247 | for (p = line; *p != '\0'; p++) |
|---|
| 248 | if (*p == '\n') |
|---|
| 249 | *p = '\0'; |
|---|
| 250 | notify(player, line); |
|---|
| 251 | } |
|---|
| 252 | } |
|---|
| 253 | if (SUPPORT_PUEBLO) |
|---|
| 254 | notify(player, close_tag("SAMP")); |
|---|
| 255 | fclose(fp); |
|---|
| 256 | if (n >= BUFFER_LEN) |
|---|
| 257 | notify_format(player, T("%s output truncated."), help_dat->command); |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | |
|---|
| 261 | static help_indx * |
|---|
| 262 | help_find_entry(help_file *help_dat, const char *the_topic) |
|---|
| 263 | { |
|---|
| 264 | help_indx *entry = NULL; |
|---|
| 265 | |
|---|
| 266 | if (help_dat->entries < 10) { /* Just do a linear search for small files */ |
|---|
| 267 | size_t n; |
|---|
| 268 | for (n = 0; n < help_dat->entries; n++) { |
|---|
| 269 | if (string_prefix(help_dat->indx[n].topic, the_topic)) { |
|---|
| 270 | entry = &help_dat->indx[n]; |
|---|
| 271 | break; |
|---|
| 272 | } |
|---|
| 273 | } |
|---|
| 274 | } else { /* Binary search of the index */ |
|---|
| 275 | int left = 0; |
|---|
| 276 | int cmp; |
|---|
| 277 | int right = help_dat->entries - 1; |
|---|
| 278 | |
|---|
| 279 | while (1) { |
|---|
| 280 | int n = (left + right) / 2; |
|---|
| 281 | |
|---|
| 282 | if (left > right) |
|---|
| 283 | break; |
|---|
| 284 | |
|---|
| 285 | cmp = strcasecmp(the_topic, help_dat->indx[n].topic); |
|---|
| 286 | |
|---|
| 287 | if (cmp == 0) { |
|---|
| 288 | entry = &help_dat->indx[n]; |
|---|
| 289 | break; |
|---|
| 290 | } else if (cmp < 0) { |
|---|
| 291 | /* We need to catch the first prefix */ |
|---|
| 292 | if (string_prefix(help_dat->indx[n].topic, the_topic)) { |
|---|
| 293 | int m; |
|---|
| 294 | for (m = n - 1; m >= 0; m--) { |
|---|
| 295 | if (!string_prefix(help_dat->indx[m].topic, the_topic)) |
|---|
| 296 | break; |
|---|
| 297 | } |
|---|
| 298 | entry = &help_dat->indx[m + 1]; |
|---|
| 299 | break; |
|---|
| 300 | } |
|---|
| 301 | if (left == right) |
|---|
| 302 | break; |
|---|
| 303 | right = n - 1; |
|---|
| 304 | } else { /* cmp > 0 */ |
|---|
| 305 | if (left == right) |
|---|
| 306 | break; |
|---|
| 307 | left = n + 1; |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | } |
|---|
| 311 | return entry; |
|---|
| 312 | } |
|---|
| 313 | |
|---|
| 314 | static void |
|---|
| 315 | write_topic(long int p) |
|---|
| 316 | { |
|---|
| 317 | tlist *cur, *nextptr; |
|---|
| 318 | help_indx *temp; |
|---|
| 319 | for (cur = top; cur; cur = nextptr) { |
|---|
| 320 | nextptr = cur->next; |
|---|
| 321 | if (num_topics >= top_topics) { |
|---|
| 322 | top_topics += top_topics / 2 + 20; |
|---|
| 323 | if (topics) |
|---|
| 324 | topics = (help_indx *) realloc(topics, top_topics * sizeof(help_indx)); |
|---|
| 325 | else |
|---|
| 326 | topics = (help_indx *) malloc(top_topics * sizeof(help_indx)); |
|---|
| 327 | if (!topics) { |
|---|
| 328 | mush_panic(T("Out of memory")); |
|---|
| 329 | } |
|---|
| 330 | } |
|---|
| 331 | temp = &topics[num_topics++]; |
|---|
| 332 | temp->pos = p; |
|---|
| 333 | strcpy(temp->topic, cur->topic); |
|---|
| 334 | free(cur); |
|---|
| 335 | } |
|---|
| 336 | top = NULL; |
|---|
| 337 | } |
|---|
| 338 | |
|---|
| 339 | static int WIN32_CDECL topic_cmp(const void *s1, const void *s2); |
|---|
| 340 | static int WIN32_CDECL |
|---|
| 341 | topic_cmp(const void *s1, const void *s2) |
|---|
| 342 | { |
|---|
| 343 | const help_indx *a = s1; |
|---|
| 344 | const help_indx *b = s2; |
|---|
| 345 | |
|---|
| 346 | return strcasecmp(a->topic, b->topic); |
|---|
| 347 | |
|---|
| 348 | } |
|---|
| 349 | |
|---|
| 350 | static void |
|---|
| 351 | help_build_index(help_file *h, int restricted) |
|---|
| 352 | { |
|---|
| 353 | long bigpos, pos = 0; |
|---|
| 354 | bool in_topic; |
|---|
| 355 | int i, lineno, ntopics; |
|---|
| 356 | size_t n; |
|---|
| 357 | char *s, *topic; |
|---|
| 358 | char the_topic[TOPIC_NAME_LEN + 1]; |
|---|
| 359 | char line[LINE_SIZE + 1]; |
|---|
| 360 | FILE *rfp; |
|---|
| 361 | tlist *cur; |
|---|
| 362 | |
|---|
| 363 | /* Quietly ignore null values for the file */ |
|---|
| 364 | if (!h || !h->file) |
|---|
| 365 | return; |
|---|
| 366 | if ((rfp = fopen(h->file, FOPEN_READ)) == NULL) { |
|---|
| 367 | do_rawlog(LT_ERR, T("Can't open %s for reading"), h->file); |
|---|
| 368 | return; |
|---|
| 369 | } |
|---|
| 370 | |
|---|
| 371 | if (restricted) |
|---|
| 372 | do_rawlog(LT_WIZ, T("Indexing file %s (admin topics)"), h->file); |
|---|
| 373 | else |
|---|
| 374 | do_rawlog(LT_WIZ, T("Indexing file %s"), h->file); |
|---|
| 375 | topics = NULL; |
|---|
| 376 | num_topics = 0; |
|---|
| 377 | top_topics = 0; |
|---|
| 378 | bigpos = 0L; |
|---|
| 379 | lineno = 0; |
|---|
| 380 | ntopics = 0; |
|---|
| 381 | |
|---|
| 382 | in_topic = 0; |
|---|
| 383 | |
|---|
| 384 | while (fgets(line, LINE_SIZE, rfp) != NULL) { |
|---|
| 385 | ++lineno; |
|---|
| 386 | if (ntopics == 0) { |
|---|
| 387 | /* Looking for the first topic, but we'll ignore blank lines */ |
|---|
| 388 | if (!line[0]) { |
|---|
| 389 | /* Someone's feeding us /dev/null? */ |
|---|
| 390 | do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"), |
|---|
| 391 | h->file); |
|---|
| 392 | fclose(rfp); |
|---|
| 393 | return; |
|---|
| 394 | } |
|---|
| 395 | if (isspace((unsigned char) line[0])) |
|---|
| 396 | continue; |
|---|
| 397 | if (line[0] != '&') { |
|---|
| 398 | do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"), |
|---|
| 399 | h->file); |
|---|
| 400 | fclose(rfp); |
|---|
| 401 | return; |
|---|
| 402 | } |
|---|
| 403 | } |
|---|
| 404 | n = strlen(line); |
|---|
| 405 | if (line[n - 1] != '\n') { |
|---|
| 406 | do_rawlog(LT_ERR, T("Line %d of %s: line too long"), lineno, h->file); |
|---|
| 407 | } |
|---|
| 408 | if (line[0] == '&') { |
|---|
| 409 | ++ntopics; |
|---|
| 410 | if (!in_topic) { |
|---|
| 411 | /* Finish up last entry */ |
|---|
| 412 | if (ntopics > 1) { |
|---|
| 413 | write_topic(pos); |
|---|
| 414 | } |
|---|
| 415 | in_topic = true; |
|---|
| 416 | } |
|---|
| 417 | /* parse out the topic */ |
|---|
| 418 | /* Get the beginning of the topic string */ |
|---|
| 419 | for (topic = &line[1]; |
|---|
| 420 | (*topic == ' ' || *topic == '\t') && *topic != '\0'; topic++) ; |
|---|
| 421 | |
|---|
| 422 | /* Get the topic */ |
|---|
| 423 | strcpy(the_topic, ""); |
|---|
| 424 | for (i = -1, s = topic; *s != '\n' && *s != '\0'; s++) { |
|---|
| 425 | if (i >= TOPIC_NAME_LEN - 1) |
|---|
| 426 | break; |
|---|
| 427 | if (*s != ' ' || the_topic[i] != ' ') |
|---|
| 428 | the_topic[++i] = *s; |
|---|
| 429 | } |
|---|
| 430 | if ((restricted && the_topic[0] == '&') |
|---|
| 431 | || (!restricted && the_topic[0] != '&')) { |
|---|
| 432 | the_topic[++i] = '\0'; |
|---|
| 433 | cur = (tlist *) malloc(sizeof(tlist)); |
|---|
| 434 | strcpy(cur->topic, the_topic); |
|---|
| 435 | cur->next = top; |
|---|
| 436 | top = cur; |
|---|
| 437 | } |
|---|
| 438 | } else { |
|---|
| 439 | if (in_topic) { |
|---|
| 440 | pos = bigpos; |
|---|
| 441 | } |
|---|
| 442 | in_topic = false; |
|---|
| 443 | } |
|---|
| 444 | bigpos = ftell(rfp); |
|---|
| 445 | } |
|---|
| 446 | |
|---|
| 447 | /* Handle last topic */ |
|---|
| 448 | write_topic(pos); |
|---|
| 449 | qsort(topics, num_topics, sizeof(help_indx), topic_cmp); |
|---|
| 450 | h->entries = num_topics; |
|---|
| 451 | h->indx = topics; |
|---|
| 452 | add_check("help_index"); |
|---|
| 453 | fclose(rfp); |
|---|
| 454 | do_rawlog(LT_WIZ, T("%d topics indexed."), num_topics); |
|---|
| 455 | return; |
|---|
| 456 | } |
|---|
| 457 | |
|---|
| 458 | /* ARGSUSED */ |
|---|
| 459 | FUNCTION(fun_textfile) |
|---|
| 460 | { |
|---|
| 461 | help_file *h; |
|---|
| 462 | |
|---|
| 463 | h = hashfind(strupper(args[0]), &help_files); |
|---|
| 464 | if (!h) { |
|---|
| 465 | safe_str(T("#-1 NO SUCH FILE"), buff, bp); |
|---|
| 466 | return; |
|---|
| 467 | } |
|---|
| 468 | if (h->admin && !Hasprivs(executor)) { |
|---|
| 469 | safe_str(T(e_perm), buff, bp); |
|---|
| 470 | return; |
|---|
| 471 | } |
|---|
| 472 | |
|---|
| 473 | if (wildcard(args[1])) { |
|---|
| 474 | char **entries; |
|---|
| 475 | int len = 0; |
|---|
| 476 | entries = list_matching_entries(args[1], h, &len); |
|---|
| 477 | if (len == 0) |
|---|
| 478 | safe_str(T("No matching help topics."), buff, bp); |
|---|
| 479 | else |
|---|
| 480 | arr2list(entries, len, buff, bp, ", "); |
|---|
| 481 | free_entry_list(entries); |
|---|
| 482 | } else |
|---|
| 483 | safe_str(string_spitfile(h, args[1]), buff, bp); |
|---|
| 484 | } |
|---|
| 485 | |
|---|
| 486 | /* ARGSUSED */ |
|---|
| 487 | FUNCTION(fun_textentries) |
|---|
| 488 | { |
|---|
| 489 | help_file *h; |
|---|
| 490 | char **entries; |
|---|
| 491 | int len = 0; |
|---|
| 492 | const char *sep = " "; |
|---|
| 493 | |
|---|
| 494 | h = hashfind(strupper(args[0]), &help_files); |
|---|
| 495 | if (!h) { |
|---|
| 496 | safe_str(T("#-1 NO SUCH FILE"), buff, bp); |
|---|
| 497 | return; |
|---|
| 498 | } |
|---|
| 499 | if (h->admin && !Hasprivs(executor)) { |
|---|
| 500 | safe_str(T(e_perm), buff, bp); |
|---|
| 501 | return; |
|---|
| 502 | } |
|---|
| 503 | if (nargs > 2) |
|---|
| 504 | sep = args[2]; |
|---|
| 505 | |
|---|
| 506 | entries = list_matching_entries(args[1], h, &len); |
|---|
| 507 | if (entries) { |
|---|
| 508 | arr2list(entries, len, buff, bp, sep); |
|---|
| 509 | free_entry_list(entries); |
|---|
| 510 | } |
|---|
| 511 | } |
|---|
| 512 | |
|---|
| 513 | static const char * |
|---|
| 514 | normalize_entry(help_file *help_dat, const char *arg1) |
|---|
| 515 | { |
|---|
| 516 | static char the_topic[LINE_SIZE + 2]; |
|---|
| 517 | |
|---|
| 518 | if (*arg1 == '\0') |
|---|
| 519 | arg1 = (char *) "help"; |
|---|
| 520 | else if (*arg1 == '&') |
|---|
| 521 | return T("#-1 INVALID ENTRY"); |
|---|
| 522 | if (help_dat->admin) |
|---|
| 523 | snprintf(the_topic, LINE_SIZE, "&%s", arg1); |
|---|
| 524 | else |
|---|
| 525 | mush_strncpy(the_topic, arg1, LINE_SIZE); |
|---|
| 526 | return the_topic; |
|---|
| 527 | } |
|---|
| 528 | |
|---|
| 529 | static const char * |
|---|
| 530 | string_spitfile(help_file *help_dat, char *arg1) |
|---|
| 531 | { |
|---|
| 532 | help_indx *entry = NULL; |
|---|
| 533 | FILE *fp; |
|---|
| 534 | char line[LINE_SIZE + 1]; |
|---|
| 535 | char the_topic[LINE_SIZE + 2]; |
|---|
| 536 | size_t n; |
|---|
| 537 | static char buff[BUFFER_LEN]; |
|---|
| 538 | char *bp; |
|---|
| 539 | |
|---|
| 540 | strcpy(the_topic, normalize_entry(help_dat, arg1)); |
|---|
| 541 | |
|---|
| 542 | if (!help_dat->indx || help_dat->entries == 0) |
|---|
| 543 | return T("#-1 NO INDEX FOR FILE"); |
|---|
| 544 | |
|---|
| 545 | entry = help_find_entry(help_dat, the_topic); |
|---|
| 546 | if (!entry) { |
|---|
| 547 | return T("#-1 NO ENTRY"); |
|---|
| 548 | } |
|---|
| 549 | |
|---|
| 550 | if ((fp = fopen(help_dat->file, FOPEN_READ)) == NULL) { |
|---|
| 551 | return T("#-1 UNAVAILABLE"); |
|---|
| 552 | } |
|---|
| 553 | if (fseek(fp, entry->pos, 0) < 0L) { |
|---|
| 554 | return T("#-1 UNAVAILABLE"); |
|---|
| 555 | } |
|---|
| 556 | bp = buff; |
|---|
| 557 | for (n = 0; n < BUFFER_LEN; n++) { |
|---|
| 558 | if (fgets(line, LINE_SIZE, fp) == NULL) |
|---|
| 559 | break; |
|---|
| 560 | if (line[0] == '&') |
|---|
| 561 | break; |
|---|
| 562 | safe_str(line, buff, &bp); |
|---|
| 563 | } |
|---|
| 564 | *bp = '\0'; |
|---|
| 565 | fclose(fp); |
|---|
| 566 | return buff; |
|---|
| 567 | } |
|---|
| 568 | |
|---|
| 569 | /** Return a string with all help entries that match a pattern */ |
|---|
| 570 | static char ** |
|---|
| 571 | list_matching_entries(const char *pattern, help_file *help_dat, int *len) |
|---|
| 572 | { |
|---|
| 573 | char **buff; |
|---|
| 574 | int offset; |
|---|
| 575 | size_t n; |
|---|
| 576 | |
|---|
| 577 | if (help_dat->admin) |
|---|
| 578 | offset = 1; /* To skip the leading & */ |
|---|
| 579 | else |
|---|
| 580 | offset = 0; |
|---|
| 581 | |
|---|
| 582 | if (!wildcard(pattern)) { |
|---|
| 583 | /* Quick way out, use the other kind of matching */ |
|---|
| 584 | char the_topic[LINE_SIZE + 2]; |
|---|
| 585 | help_indx *entry = NULL; |
|---|
| 586 | strcpy(the_topic, normalize_entry(help_dat, pattern)); |
|---|
| 587 | if (!help_dat->indx || help_dat->entries == 0) { |
|---|
| 588 | *len = 0; |
|---|
| 589 | return NULL; |
|---|
| 590 | } |
|---|
| 591 | entry = help_find_entry(help_dat, the_topic); |
|---|
| 592 | if (!entry) { |
|---|
| 593 | *len = 0; |
|---|
| 594 | return NULL; |
|---|
| 595 | } else { |
|---|
| 596 | *len = 1; |
|---|
| 597 | buff = mush_malloc(sizeof(char **), "help.search"); |
|---|
| 598 | *buff = entry->topic + offset; |
|---|
| 599 | return buff; |
|---|
| 600 | } |
|---|
| 601 | } |
|---|
| 602 | |
|---|
| 603 | buff = mush_calloc(help_dat->entries, sizeof(char *), "help.search"); |
|---|
| 604 | *len = 0; |
|---|
| 605 | |
|---|
| 606 | for (n = 0; n < help_dat->entries; n++) |
|---|
| 607 | if (quick_wild(pattern, help_dat->indx[n].topic + offset)) { |
|---|
| 608 | buff[*len] = help_dat->indx[n].topic + offset; |
|---|
| 609 | *len += 1; |
|---|
| 610 | } |
|---|
| 611 | |
|---|
| 612 | return buff; |
|---|
| 613 | } |
|---|
| 614 | |
|---|
| 615 | static void |
|---|
| 616 | free_entry_list(char **entries) |
|---|
| 617 | { |
|---|
| 618 | if (entries) |
|---|
| 619 | mush_free(entries, "help.search"); |
|---|
| 620 | } |
|---|