/* * history.c: stuff to handle command line history * * Written By Michael Sandrof * * Copyright (c) 1990 Michael Sandrof. * Copyright (c) 1991, 1992 Troy Rollo. * Copyright (c) 1992-2000 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. */ #include "irc.h" IRCII_RCSID("@(#)$Id: history.c,v 1.18 2000/07/18 17:12:19 mrg Exp $"); #include "ircaux.h" #include "vars.h" #include "history.h" #include "output.h" #include "input.h" static int parse_history _((u_char *, u_char **)); static u_char *history_match _((u_char *)); static void add_to_history_file _((int, u_char *)); static void add_to_history_list _((int, u_char *)); static u_char *get_from_history_file _((int)); static u_char *get_from_history_buffer _((int)); static FILE *hist_file = (FILE *) 0; typedef struct HistoryStru { int number; u_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 u_char * history_match(match) u_char *match; { u_char *ptr; u_char *match_str = (u_char *) 0; u_char buffer[BIG_BUFFER_SIZE]; if (*(match + my_strlen(match) - 1) == '*') malloc_strcpy(&match_str, match); else { match_str = (u_char *) new_malloc(my_strlen(match) + 2); my_strcpy(match_str, match); my_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(CP(buffer), BIG_BUFFER_SIZE, hist_file)) { parse_history(buffer, &ptr); if (wild_match(match_str, ptr)) { new_free(&match_str); *(ptr + my_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 (u_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; u_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; u_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; 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 = (u_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) u_char *file; { u_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, (u_char *) 0); return; } #endif /* DAEMON_UID */ if ((ptr = expand_twiddle(file)) == (u_char *) 0) { say("Bad filename: %s",file); set_string_var(HISTORY_FILE_VAR, (u_char *) 0); return; } set_string_var(HISTORY_FILE_VAR, ptr); if (hist_file) fclose(hist_file); fd = open(CP(ptr), O_WRONLY|O_CREAT|O_APPEND, 0600); 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, (u_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; 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) u_char *lbuf; u_char **ret; { u_char *ptr; int entry; if ((ptr = my_index(lbuf, ':')) != NULL) { entry = my_atoi(lbuf); *ret = ptr + 2; return (entry); } *ret = (u_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) u_char *line; { u_char *ptr; if (line && *line) while (line) { if ((ptr = sindex(line, UP("\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 u_char * get_from_history_file(which) int which; { u_char *ptr; u_char buffer[BIG_BUFFER_SIZE]; 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(CP(buffer), BIG_BUFFER_SIZE, hist_file)) { file_pos = 0L; fseek(hist_file, 0L, 0); if (!fgets(CP(buffer), BIG_BUFFER_SIZE, hist_file)) return (u_char *) 0; } } else if (!rfgets(CP(buffer), BIG_BUFFER_SIZE, hist_file)) { fseek(hist_file, 0L, 2); file_pos = ftell(hist_file); if (!rfgets(CP(buffer), BIG_BUFFER_SIZE, hist_file)) return (u_char *) 0; } file_pos = ftell(hist_file); buffer[my_strlen(buffer) - 1] = '\0'; parse_history(buffer, &ptr); return (ptr); } static u_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); } } u_char * get_from_history(which) int which; { u_char *str = (u_char *) 0; 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) u_char *command, *args, *subargs; { int cnt, max; u_char *value; u_char buffer[BIG_BUFFER_SIZE]; History *tmp; say("Command History:"); if (hist_file) { if ((value = next_arg(args, &args)) != NULL) { if ((max = my_atoi(value)) > hist_count) max = 0; else max = hist_count - max + 1; } else max = 0; fseek(hist_file, 0L, 0); while (--max > 0) fgets(CP(buffer), BIG_BUFFER_SIZE, hist_file); while (fgets(CP(buffer), BIG_BUFFER_SIZE, hist_file)) { buffer[my_strlen(buffer) - 1] = '\0'; put_it("%s", buffer); } } else if (get_int_var(HISTORY_VAR)) { if ((value = next_arg(args, &args)) != NULL) { if ((max = my_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 */ u_char * do_history(com, rest) u_char *com, *rest; { int hist_num; u_char *ptr, *ret = (u_char *) 0; u_char buffer[BIG_BUFFER_SIZE]; static u_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 = (u_char *) new_malloc(my_strlen(ptr) + my_strlen(rest) + 2); my_strcpy(ret, ptr); my_strcat(ret, " "); my_strcat(ret, rest); } else malloc_strcpy(&ret, ptr); } else say("No Match"); return (ret); } hist_num = my_atoi(com); if (hist_file) { fseek(hist_file, 0L, 0); while (fgets(CP(buffer), BIG_BUFFER_SIZE, hist_file)) if (parse_history(buffer, &ptr) == hist_num) { *(ptr + my_strlen(ptr) - 1) = '\0'; if (rest && *rest) { ret = (u_char *) new_malloc(my_strlen(ptr) + my_strlen(rest) + 2); my_strcpy(ret, ptr); my_strcat(ret, " "); my_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 = (u_char *) new_malloc(my_strlen(tmp->stuff) + my_strlen(rest) + 2); my_strcpy(ret, tmp->stuff); my_strcat(ret, " "); my_strcat(ret, rest); } else malloc_strcpy(&ret, tmp->stuff); return (ret); } say("No such history entry: %d", hist_num); } return (u_char *) 0; } .