/* * history.c: stuff to handle command line history * * Copyright (c) 1990 Michael Sandrof. * Copyright (c) 1991, 1992 Troy Rollo. * Copyright (c) 1992-2003 Matthew R. Green. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: history.c,v 1.9 2003/01/08 20:00:54 f Exp $ */ #include "irc.h" #include "ircaux.h" #include "vars.h" #include "history.h" #include "output.h" #include "input.h" static int parse_history _((char *, char **)); static char *history_match _((char *)); static void add_to_history_file _((int, char *)); static void add_to_history_list _((int, char *)); static char *get_from_history_file _((int)); static char *get_from_history_buffer _((int)); static FILE *hist_file = (FILE *) 0; typedef struct HistoryStru { int number; char *stuff; struct HistoryStru *next; struct HistoryStru *prev; } History; /* command_history: pointer to head of command_history list */ static History *command_history_head = (History *) 0; static History *command_history_tail = (History *) 0; static History *command_history_pos = (History *) 0; /* hist_size: the current size of the command_history array */ static int hist_size = 0; /* hist_count: the absolute counter for the history list */ static int hist_count = 0; /* * last_dir: the direction (next or previous) of the last get_from_history() * call.... reset by add to history */ static int last_dir = -1; /* * file_pos: The position in the history file of the last history entry * zwooped out by get_from_history_file()... look there for how this is used */ static off_t file_pos = 0; /* * history pointer */ static History *history_tmp = (History *) 0; /* * history_match: using wild_match(), this finds the latest match in the * history file and returns it as the function result. Returns null if there * is no match. Note that this sticks a '*' at the end if one is not already * there. */ static char * history_match(match) char *match; { char *ptr; char *match_str = (char *)NULL; /**************************** PATCHED by Flier ******************************/ /*char buffer[BIG_BUFFER_SIZE+1];*/ static char buffer[BIG_BUFFER_SIZE+1]; /****************************************************************************/ if (*(match + strlen(match) - 1) == '*') malloc_strcpy(&match_str, match); else { match_str = (char *) new_malloc(strlen(match) + 2); strcpy(match_str, match); strcat(match_str, "*"); } if (hist_file) { if (last_dir == -1) fseek(hist_file, 0L, 2); else fseek(hist_file, (long)file_pos, 0); while (rfgets(buffer, BIG_BUFFER_SIZE, hist_file)) { parse_history(buffer, &ptr); if (wild_match(match_str, ptr)) { new_free(&match_str); *(ptr + strlen(ptr) - 1) = '\0'; file_pos = ftell(hist_file); last_dir = PREV; return (ptr); } } file_pos = 0; } if (!hist_file && get_int_var(HISTORY_VAR)) { if ((last_dir == -1) || (history_tmp == (History *) 0)) history_tmp = command_history_head; else history_tmp = history_tmp->next; for (; history_tmp; history_tmp = history_tmp->next) if (wild_match(match_str, history_tmp->stuff)) { new_free(&match_str); last_dir = PREV; return (history_tmp->stuff); } } last_dir = -1; new_free(&match_str); return (char *) 0; } /* * add_to_history_file: This adds the given line of text to the end of the * history file using cnt as the history index number. */ static void add_to_history_file(cnt, line) int cnt; char *line; { if (hist_file) { fseek(hist_file, 0L, 2); fprintf(hist_file, "%d: %s\n", cnt, line); fflush(hist_file); file_pos = ftell(hist_file); } } static void add_to_history_list(cnt, stuff) int cnt; char *stuff; { History *new; if (get_int_var(HISTORY_VAR) == 0) return; if ((hist_size == get_int_var(HISTORY_VAR)) && command_history_tail) { if (hist_size == 1) { malloc_strcpy(&command_history_tail->stuff, stuff); return; } new = command_history_tail; command_history_tail = command_history_tail->prev; command_history_tail->next = (History *) 0; /**************************** Patched by Flier ******************************/ /* fix possible crash in history search when history_tmp is freed */ if (history_tmp==new) history_tmp=(History *) 0; /****************************************************************************/ new_free(&new->stuff); new_free(&new); if (command_history_tail == (History *) 0) command_history_head = (History *) 0; } else hist_size++; new = (History *) new_malloc(sizeof(History)); new->stuff = (char *) 0; new->number = cnt; new->next = command_history_head; new->prev = (History *) 0; malloc_strcpy(&(new->stuff), stuff); if (command_history_head) command_history_head->prev = new; command_history_head = new; if (command_history_tail == (History *) 0) command_history_tail = new; command_history_pos = (History *) 0; } /* * set_history_file: this sets the file to be used by the command history * function to whatever you send as file. This expands twiddles and opens * the file if all is well */ void set_history_file(file) char *file; { char *ptr; int i, cnt, fd; History *tmp; if (file) { #ifdef DAEMON_UID if (getuid() == DAEMON_UID) { say("You are not permitted to use a HISTORY_FILE"); set_string_var(HISTORY_FILE_VAR, (char *) 0); return; } #endif /* DAEMON_UID */ if ((ptr = expand_twiddle(file)) == (char *) 0) { say("Bad filename: %s",file); set_string_var(HISTORY_FILE_VAR, (char *) 0); return; } set_string_var(HISTORY_FILE_VAR, ptr); if (hist_file) fclose(hist_file); /************************* PATCHED by Flier *************************/ /*fd = open(ptr, O_WRONLY|O_CREAT|O_APPEND, 0600);*/ /* open() and fdopen() modes must be compatible on Linux */ #ifdef __linux__ fd=open(ptr,O_RDWR|O_CREAT|O_APPEND,0600); #else fd=open(ptr,O_WRONLY|O_CREAT|O_APPEND,0600); #endif /********************************************************************/ if (fd < 0 || ((hist_file = fdopen(fd, "w+")) == (FILE *) 0)) { say("Unable to open %s: %s", ptr, strerror(errno)); set_string_var(HISTORY_FILE_VAR, (char *) 0); hist_file = (FILE *) 0; } else if (hist_size) { cnt = hist_size; tmp = command_history_tail; for (i = 0; i < cnt; i++, tmp = tmp->prev) add_to_history_file(tmp->number, tmp->stuff); } new_free(&ptr); } else if (hist_file) { fclose(hist_file); hist_file = (FILE *) 0; } } /* * set_history_size: adjusts the size of the command_history to be size. If * the array is not yet allocated, it is set to size and all the entries * nulled. If it exists, it is resized to the new size with a realloc. Any * new entries are nulled. */ void set_history_size(size) int size; { int i, cnt; History *ptr; if (size < hist_size) { cnt = hist_size - size; for (i = 0; i < cnt; i++) { ptr = command_history_tail; command_history_tail = ptr->prev; /**************************** Patched by Flier ******************************/ /* fix possible crash in history search when history_tmp is freed */ if (history_tmp==ptr) history_tmp=(History *) 0; /****************************************************************************/ new_free(&(ptr->stuff)); new_free(&ptr); } if (command_history_tail == (History *) 0) command_history_head = (History *) 0; else command_history_tail->next = (History *) 0; hist_size = size; } } /* * parse_history: given a string of the form "number: stuff", this returns * the number as an integer and points ret to stuff */ static int parse_history(lbuf, ret) char *lbuf; char **ret; { char *ptr; int entry; if ((ptr = index(lbuf, ':')) != NULL) { entry = atoi(lbuf); *ret = ptr + 2; return (entry); } *ret = (char *) 0; return -1; } /* * add_to_history: adds the given line to the history array. The history * array is a circular buffer, and add_to_history handles all that stuff. It * automagically allocted and deallocated memory as needed */ void add_to_history(line) char *line; { char *ptr; if (line && *line) while (line) { if ((ptr = sindex(line, "\n\r")) != NULL) *(ptr++) = '\0'; add_to_history_list(hist_count, line); add_to_history_file(hist_count, line); last_dir = PREV; hist_count++; line = ptr; } } static char * get_from_history_file(which) int which; { char *ptr; /**************************** PATCHED by Flier ******************************/ /*char buffer[BIG_BUFFER_SIZE+1];*/ static char buffer[BIG_BUFFER_SIZE+1]; /****************************************************************************/ if (last_dir == -1) last_dir = which; else if (last_dir != which) { last_dir = which; get_from_history(which); } fseek(hist_file, (long)file_pos, 0); if (which == NEXT) { if (!fgets(buffer, BIG_BUFFER_SIZE, hist_file)) { file_pos = 0L; fseek(hist_file, 0L, 0); if (!fgets(buffer, BIG_BUFFER_SIZE, hist_file)) return (char *) 0; } } else if (!rfgets(buffer, BIG_BUFFER_SIZE, hist_file)) { fseek(hist_file, 0L, 2); file_pos = ftell(hist_file); if (!rfgets(buffer, BIG_BUFFER_SIZE, hist_file)) return (char *) 0; } file_pos = ftell(hist_file); buffer[strlen(buffer) - 1] = '\0'; parse_history(buffer, &ptr); return (ptr); } static char * get_from_history_buffer(which) int which; { if ((get_int_var(HISTORY_VAR) == 0) || (hist_size == 0)) return (u_char *) 0; /* * if (last_dir != which) { last_dir = which; get_from_history(which); } */ if (which == NEXT) { if (command_history_pos) { if (command_history_pos->prev) command_history_pos = command_history_pos->prev; else command_history_pos = command_history_tail; } else { add_to_history(get_input()); command_history_pos = command_history_tail; } return (command_history_pos->stuff); } else { if (command_history_pos) { if (command_history_pos->next) command_history_pos = command_history_pos->next; else command_history_pos = command_history_head; } else { add_to_history(get_input()); command_history_pos = command_history_head; } return (command_history_pos->stuff); } } char * get_from_history(which) int which; { char *str = (char *)NULL; if (get_string_var(HISTORY_FILE_VAR)) str = get_from_history_file(which); if (!str) str = get_from_history_buffer(which); return str; } /* history: the /HISTORY command, shows the command history buffer. */ /*ARGSUSED*/ void history(command, args, subargs) char *command, *args, *subargs; { int cnt, max; char *value; char buffer[BIG_BUFFER_SIZE+1]; History *tmp; say("Command History:"); if (hist_file) { if ((value = next_arg(args, &args)) != NULL) { if ((max = atoi(value)) > hist_count) max = 0; else max = hist_count - max + 1; } else max = 0; fseek(hist_file, 0L, 0); while (--max > 0) fgets(buffer, BIG_BUFFER_SIZE, hist_file); while (fgets(buffer, BIG_BUFFER_SIZE, hist_file)) { buffer[strlen(buffer) - 1] = '\0'; put_it("%s", buffer); } } else if (get_int_var(HISTORY_VAR)) { if ((value = next_arg(args, &args)) != NULL) { if ((max = atoi(value)) > get_int_var(HISTORY_VAR)) max = get_int_var(HISTORY_VAR); } else max = get_int_var(HISTORY_VAR); for (tmp = command_history_tail, cnt = 0; tmp && (cnt < max); tmp = tmp->prev, cnt++) put_it("%d: %s", tmp->number, tmp->stuff); } } /* * do_history: This finds the given history entry in either the history file, * or the in memory history buffer (if no history file is given). It then * returns the found entry as its function value, or null if the entry is not * found for some reason. Note that this routine mallocs the string returned */ char * do_history(com, rest) char *com, *rest; { int hist_num; char *ptr, *ret = (char *)NULL; char buffer[BIG_BUFFER_SIZE+1]; static char *last_com = (u_char *) 0; if (!com || !*com) { if (last_com) com = last_com; else com = empty_string; } else /* last_dir = -1; */ malloc_strcpy(&last_com, com); if (!is_number(com)) { if ((ptr = history_match(com)) != NULL) { if (rest && *rest) { ret = (char *) new_malloc(strlen(ptr) + strlen(rest) + 2); strcpy(ret, ptr); strcat(ret, " "); strcat(ret, rest); } else malloc_strcpy(&ret, ptr); } else say("No Match"); return (ret); } hist_num = atoi(com); if (hist_file) { fseek(hist_file, 0L, 0); while (fgets(buffer, BIG_BUFFER_SIZE, hist_file)) if (parse_history(buffer, &ptr) == hist_num) { *(ptr + strlen(ptr) - 1) = '\0'; if (rest && *rest) { ret = (char *) new_malloc(strlen(ptr) + strlen(rest) + 2); strcpy(ret, ptr); strcat(ret, " "); strcat(ret, rest); } else malloc_strcpy(&ret, ptr); last_dir = PREV; file_pos = ftell(hist_file); return (ret); } say("No such history entry: %d", hist_num); file_pos = 0; } else { History *tmp; for (tmp = command_history_head; tmp; tmp = tmp->next) if (tmp->number == hist_num) { if (rest && *rest) { ret = (char *) new_malloc(strlen(tmp->stuff) + strlen(rest) + 2); strcpy(ret, tmp->stuff); strcat(ret, " "); strcat(ret, rest); } else malloc_strcpy(&ret, tmp->stuff); return (ret); } say("No such history entry: %d", hist_num); } return (char *) 0; }