/* * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include "ioutil.h" #include "history.h" #include "freelist.h" #include "errmsg.h" /* * History lines are split into sub-strings of GLH_SEG_SIZE * characters. To avoid wasting space in the GlhLineSeg structure, * this should be a multiple of the size of a pointer. */ #define GLH_SEG_SIZE 16 /* * GlhLineSeg structures contain fixed sized segments of a larger * string. These are linked into lists to record strings, with all but * the last segment having GLH_SEG_SIZE characters. The last segment * of a string is terminated within the GLH_SEG_SIZE characters with a * '\0'. */ typedef struct GlhLineSeg GlhLineSeg; struct GlhLineSeg { GlhLineSeg *next; /* The next sub-string of the history line */ char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */ /* substring of a line, as indicated by 'next' */ /* being NULL, is '\0' terminated. */ }; /* * History lines are recorded in a hash table, such that repeated * lines are stored just once. * * Start by defining the size of the hash table. This should be a * prime number. */ #define GLH_HASH_SIZE 113 typedef struct GlhHashBucket GlhHashBucket; /* * Each history line will be represented in the hash table by a * structure of the following type. */ typedef struct GlhHashNode GlhHashNode; struct GlhHashNode { GlhHashBucket *bucket; /* The parent hash-table bucket of this node */ GlhHashNode *next; /* The next in the list of nodes within the */ /* parent hash-table bucket. */ GlhLineSeg *head; /* The list of sub-strings which make up a line */ int len; /* The length of the line, excluding any '\0' */ int used; /* The number of times this string is pointed to by */ /* the time-ordered list of history lines. */ int reported; /* A flag that is used when searching to ensure that */ /* a line isn't reported redundantly. */ }; /* * How many new GlhHashNode elements should be allocated at a time? */ #define GLH_HASH_INCR 50 static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n); static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix); static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim); /* * All history lines which hash to a given bucket in the hash table, are * recorded in a structure of the following type. */ struct GlhHashBucket { GlhHashNode *lines; /* The list of history lines which fall in this bucket */ }; static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, size_t n); static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, size_t n); typedef struct { FreeList *node_mem; /* A free-list of GlhHashNode structures */ GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */ } GlhLineHash; /* * GlhLineNode's are used to record history lines in time order. */ typedef struct GlhLineNode GlhLineNode; struct GlhLineNode { long id; /* The unique identifier of this history line */ time_t timestamp; /* The time at which the line was archived */ unsigned group; /* The identifier of the history group to which the */ /* the line belongs. */ GlhLineNode *next; /* The next youngest line in the list */ GlhLineNode *prev; /* The next oldest line in the list */ GlhHashNode *line; /* The hash-table entry of the history line */ }; /* * The number of GlhLineNode elements per freelist block. */ #define GLH_LINE_INCR 100 /* * Encapsulate the time-ordered list of historical lines. */ typedef struct { FreeList *node_mem; /* A freelist of GlhLineNode objects */ GlhLineNode *head; /* The oldest line in the list */ GlhLineNode *tail; /* The newest line in the list */ } GlhLineList; /* * The _glh_lookup_history() returns copies of history lines in a * dynamically allocated array. This array is initially allocated * GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be * too small, realloc() is used to increase its size to the required * size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to * reduce the number of realloc() operations needed. */ #define GLH_LBUF_SIZE 300 #define GLH_LBUF_MARGIN 100 /* * Encapsulate all of the resources needed to store historical input lines. */ struct GlHistory { ErrMsg *err; /* The error-reporting buffer */ GlhLineSeg *buffer; /* An array of sub-line nodes to be partitioned */ /* into lists of sub-strings recording input lines. */ int nbuff; /* The allocated dimension of buffer[] */ GlhLineSeg *unused; /* The list of free nodes in buffer[] */ GlhLineList list; /* A time ordered list of history lines */ GlhLineNode *recall; /* The last line recalled, or NULL if no recall */ /* session is currently active. */ GlhLineNode *id_node;/* The node at which the last ID search terminated */ GlhLineHash hash; /* A hash-table of reference-counted history lines */ GlhHashNode *prefix; /* A pointer to a line containing the prefix that */ /* is being searched for. Note that if prefix==NULL */ /* and prefix_len>0, this means that no line in */ /* the buffer starts with the requested prefix. */ int prefix_len; /* The length of the prefix being searched for. */ char *lbuf; /* The array in which _glh_lookup_history() returns */ /* history lines */ int lbuf_dim; /* The allocated size of lbuf[] */ int nbusy; /* The number of line segments in buffer[] that are */ /* currently being used to record sub-lines */ int nfree; /* The number of line segments in buffer that are */ /* not currently being used to record sub-lines */ unsigned long seq; /* The next ID to assign to a line node */ unsigned group; /* The identifier of the current history group */ int nline; /* The number of lines currently in the history list */ int max_lines; /* Either -1 or a ceiling on the number of lines */ int enable; /* If false, ignore history additions and lookups */ }; #ifndef WITHOUT_FILE_SYSTEM static int _glh_cant_load_history(GlHistory *glh, const char *filename, int lineno, const char *message, FILE *fp); static int _glh_cant_save_history(GlHistory *glh, const char *message, const char *filename, FILE *fp); static int _glh_write_timestamp(FILE *fp, time_t timestamp); static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp); #endif static void _glh_discard_line(GlHistory *glh, GlhLineNode *node); static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id); static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, size_t n); static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode); static int _glh_prepare_for_recall(GlHistory *glh, char *line); /* * The following structure and functions are used to iterate through * the characters of a segmented history line. */ typedef struct { GlhLineSeg *seg; /* The line segment that the next character will */ /* be returned from. */ int posn; /* The index in the above line segment, containing */ /* the next unread character. */ char c; /* The current character in the input line */ } GlhLineStream; static void glh_init_stream(GlhLineStream *str, GlhHashNode *line); static void glh_step_stream(GlhLineStream *str); /* * See if search prefix contains any globbing characters. */ static int glh_contains_glob(GlhHashNode *prefix); /* * Match a line against a search pattern. */ static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr); static int glh_matches_range(char c, GlhLineStream *pstr); /*....................................................................... * Create a line history maintenance object. * * Input: * buflen size_t The number of bytes to allocate to the * buffer that is used to record all of the * most recent lines of user input that will fit. * If buflen==0, no buffer will be allocated. * Output: * return GlHistory * The new object, or NULL on error. */ GlHistory *_new_GlHistory(size_t buflen) { GlHistory *glh; /* The object to be returned */ int i; /* * Allocate the container. */ glh = (GlHistory *) malloc(sizeof(GlHistory)); if(!glh) { errno = ENOMEM; return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to _del_GlHistory(). */ glh->err = NULL; glh->buffer = NULL; glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; glh->unused = NULL; glh->list.node_mem = NULL; glh->list.head = glh->list.tail = NULL; glh->recall = NULL; glh->id_node = NULL; glh->hash.node_mem = NULL; for(i=0; ihash.bucket[i].lines = NULL; glh->prefix = NULL; glh->lbuf = NULL; glh->lbuf_dim = 0; glh->nbusy = 0; glh->nfree = glh->nbuff; glh->seq = 0; glh->group = 0; glh->nline = 0; glh->max_lines = -1; glh->enable = 1; /* * Allocate a place to record error messages. */ glh->err = _new_ErrMsg(); if(!glh->err) return _del_GlHistory(glh); /* * Allocate the buffer, if required. */ if(glh->nbuff > 0) { glh->nbuff = glh->nfree; glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff); if(!glh->buffer) { errno = ENOMEM; return _del_GlHistory(glh); }; /* * All nodes of the buffer are currently unused, so link them all into * a list and make glh->unused point to the head of this list. */ glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; }; /* * Allocate the GlhLineNode freelist. */ glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR); if(!glh->list.node_mem) return _del_GlHistory(glh); /* * Allocate the GlhHashNode freelist. */ glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR); if(!glh->hash.node_mem) return _del_GlHistory(glh); /* * Allocate the array that _glh_lookup_history() uses to return a * copy of a given history line. This will be resized when necessary. */ glh->lbuf_dim = GLH_LBUF_SIZE; glh->lbuf = (char *) malloc(glh->lbuf_dim); if(!glh->lbuf) { errno = ENOMEM; return _del_GlHistory(glh); }; return glh; } /*....................................................................... * Delete a GlHistory object. * * Input: * glh GlHistory * The object to be deleted. * Output: * return GlHistory * The deleted object (always NULL). */ GlHistory *_del_GlHistory(GlHistory *glh) { if(glh) { /* * Delete the error-message buffer. */ glh->err = _del_ErrMsg(glh->err); /* * Delete the buffer. */ if(glh->buffer) { free(glh->buffer); glh->buffer = NULL; glh->unused = NULL; }; /* * Delete the freelist of GlhLineNode's. */ glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1); /* * The contents of the list were deleted by deleting the freelist. */ glh->list.head = NULL; glh->list.tail = NULL; /* * Delete the freelist of GlhHashNode's. */ glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1); /* * Delete the lookup buffer. */ if(glh->lbuf) free(glh->lbuf); /* * Delete the container. */ free(glh); }; return NULL; } /*....................................................................... * Append a new line to the history list, deleting old lines to make * room, if needed. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The line to be archived. * force int Unless this flag is non-zero, empty lines aren't * archived. This flag requests that the line be * archived regardless. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_add_history(GlHistory *glh, const char *line, int force) { int slen; /* The length of the line to be recorded (minus the '\0') */ int empty; /* True if the string is empty */ const char *nlptr; /* A pointer to a newline character in line[] */ GlhHashNode *hnode; /* The hash-table node of the line */ GlhLineNode *lnode; /* A node in the time-ordered list of lines */ int i; /* * Check the arguments. */ if(!glh || !line) { errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Cancel any ongoing search. */ if(_glh_cancel_search(glh)) return 1; /* * How long is the string to be recorded, being careful not to include * any terminating '\n' character. */ nlptr = strchr(line, '\n'); if(nlptr) slen = (nlptr - line); else slen = strlen(line); /* * Is the line empty? */ empty = 1; for(i=0; imax_lines >= 0) { /* * If necessary, remove old lines until there is room to add one new * line without exceeding the specified line limit. */ while(glh->nline > 0 && glh->nline >= glh->max_lines) _glh_discard_line(glh, glh->list.head); /* * We can't archive the line if the maximum number of lines allowed is * zero. */ if(glh->max_lines == 0) return 0; }; /* * Unless already stored, store a copy of the line in the history buffer, * then return a reference-counted hash-node pointer to this copy. */ hnode = _glh_acquire_copy(glh, line, slen); if(!hnode) { _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); errno = ENOMEM; return 1; }; /* * Allocate a new node in the time-ordered list of lines. */ lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem); /* * If a new line-node couldn't be allocated, discard our copy of the * stored line before reporting the error. */ if(!lnode) { hnode = _glh_discard_copy(glh, hnode); _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); errno = ENOMEM; return 1; }; /* * Record a pointer to the hash-table record of the line in the new * list node. */ lnode->id = glh->seq++; lnode->timestamp = time(NULL); lnode->group = glh->group; lnode->line = hnode; /* * Append the new node to the end of the time-ordered list. */ if(glh->list.head) glh->list.tail->next = lnode; else glh->list.head = lnode; lnode->next = NULL; lnode->prev = glh->list.tail; glh->list.tail = lnode; /* * Record the addition of a line to the list. */ glh->nline++; return 0; } /*....................................................................... * Recall the next oldest line that has the search prefix last recorded * by _glh_search_prefix(). * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, if anything * was found, its contents will have been replaced * with the matching line. * dim size_t The allocated dimension of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ GlhHashNode *old_line; /* The previous recalled line */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * From where should we start the search? */ if(glh->recall) { node = glh->recall->prev; old_line = glh->recall->line; } else { node = glh->list.tail; old_line = NULL; }; /* * Search backwards through the list for the first match with the * prefix string that differs from the last line that was recalled. */ while(node && (node->group != glh->group || node->line == old_line || !_glh_line_matches_prefix(node->line, glh->prefix))) node = node->prev; /* * Was a matching line found? */ if(node) { /* * Recall the found node as the starting point for subsequent * searches. */ glh->recall = node; /* * Copy the matching line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * Return it. */ return line; }; /* * No match was found. */ return NULL; } /*....................................................................... * Recall the next newest line that has the search prefix last recorded * by _glh_search_prefix(). * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, if anything * was found, its contents will have been replaced * with the matching line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * The line requested, or NULL if no matching line * was found. */ char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ GlhHashNode *old_line; /* The previous recalled line */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * From where should we start the search? */ if(glh->recall) { node = glh->recall->next; old_line = glh->recall->line; } else { return NULL; }; /* * Search forwards through the list for the first match with the * prefix string. */ while(node && (node->group != glh->group || node->line == old_line || !_glh_line_matches_prefix(node->line, glh->prefix))) node = node->next; /* * Was a matching line found? */ if(node) { /* * Copy the matching line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * Record the starting point of the next search. */ glh->recall = node; /* * If we just returned the line that was being entered when the search * session first started, cancel the search. */ if(node == glh->list.tail) _glh_cancel_search(glh); /* * Return the matching line to the user. */ return line; }; /* * No match was found. */ return NULL; } /*....................................................................... * If a search is in progress, cancel it. * * This involves discarding the line that was temporarily saved by * _glh_find_backwards() when the search was originally started, * and reseting the search iteration pointer to NULL. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_cancel_search(GlHistory *glh) { /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * If there wasn't a search in progress, do nothing. */ if(!glh->recall) return 0; /* * Reset the search pointers. Note that it is essential to set * glh->recall to NULL before calling _glh_discard_line(), to avoid an * infinite recursion. */ glh->recall = NULL; /* * Delete the node of the preserved line. */ _glh_discard_line(glh, glh->list.tail); return 0; } /*....................................................................... * Set the prefix of subsequent history searches. * * Input: * glh GlHistory * The input-line history maintenance object. * line const char * The command line who's prefix is to be used. * prefix_len int The length of the prefix. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len) { /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Discard any existing prefix. */ glh->prefix = _glh_discard_copy(glh, glh->prefix); /* * Only store a copy of the prefix string if it isn't a zero-length string. */ if(prefix_len > 0) { /* * Get a reference-counted copy of the prefix from the history cache buffer. */ glh->prefix = _glh_acquire_copy(glh, line, prefix_len); /* * Was there insufficient buffer space? */ if(!glh->prefix) { _err_record_msg(glh->err, "The search prefix is too long to store", END_ERR_MSG); errno = ENOMEM; return 1; }; }; return 0; } /*....................................................................... * Recall the oldest recorded line. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the oldest line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * Locate the oldest line that belongs to the current group. */ for(node=glh->list.head; node && node->group != glh->group; node = node->next) ; /* * No line found? */ if(!node) return NULL; /* * Record the above node as the starting point for subsequent * searches. */ glh->recall = node; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(node->line, line, dim); /* * If we just returned the line that was being entered when the search * session first started, cancel the search. */ if(node == glh->list.tail) _glh_cancel_search(glh); return line; } /*....................................................................... * Recall the line that was being entered when the search started. * * Input: * glh GlHistory * The input-line history maintenance object. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the line that was * being entered when the search was started. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_current_line(GlHistory *glh, char *line, size_t dim) { /* * Check the arguments. */ if(!glh || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * If history isn't enabled, or no history search has yet been started, * ignore the call. */ if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall) return NULL; /* * Check the line dimensions. */ if(dim < strlen(line) + 1) { _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", END_ERR_MSG); errno = EINVAL; return NULL; }; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(glh->list.tail->line, line, dim); /* * Since we have returned to the starting point of the search, cancel it. */ _glh_cancel_search(glh); return line; } /*....................................................................... * Query the id of a history line offset by a given number of lines from * the one that is currently being recalled. If a recall session isn't * in progress, or the offset points outside the history list, 0 is * returned. * * Input: * glh GlHistory * The input-line history maintenance object. * offset int The line offset (0 for the current line, < 0 * for an older line, > 0 for a newer line. * Output: * return GlhLineID The identifier of the line that is currently * being recalled, or 0 if no recall session is * currently in progress. */ GlhLineID _glh_line_id(GlHistory *glh, int offset) { GlhLineNode *node; /* The line location node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return 0; /* * Search forward 'offset' lines to find the required line. */ if(offset >= 0) { for(node=glh->recall; node && offset != 0; node=node->next) { if(node->group == glh->group) offset--; }; } else { for(node=glh->recall; node && offset != 0; node=node->prev) { if(node->group == glh->group) offset++; }; }; return node ? node->id : 0; } /*....................................................................... * Recall a line by its history buffer ID. If the line is no longer * in the buffer, or the id is zero, NULL is returned. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlhLineID The ID of the line to be returned. * line char * The input line buffer. On input this should contain * the current input line, and on output, its contents * will have been replaced with the saved line. * dim size_t The allocated dimensions of the line buffer. * Output: * return char * A pointer to line[0], or NULL if not found. */ char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim) { GlhLineNode *node; /* The line location node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->buffer || glh->max_lines == 0) return NULL; /* * Preserve the input line if needed. */ if(_glh_prepare_for_recall(glh, line)) return NULL; /* * Search for the specified line. */ node = _glh_find_id(glh, id); /* * Not found? */ if(!node || node->group != glh->group) return NULL; /* * Record the node of the matching line as the starting point * for subsequent searches. */ glh->recall = node; /* * Copy the recalled line into the provided line buffer. */ _glh_return_line(node->line, line, dim); return line; } /*....................................................................... * Save the current history in a specified file. * * Input: * glh GlHistory * The input-line history maintenance object. * filename const char * The name of the new file to record the * history in. * comment const char * Extra information such as timestamps will * be recorded on a line started with this * string, the idea being that the file can * double as a command file. Specify "" if * you don't care. * max_lines int The maximum number of lines to save, or -1 * to save all of the lines in the history * list. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_save_history(GlHistory *glh, const char *filename, const char *comment, int max_lines) { #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(glh->err, "Can't save history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FILE *fp; /* The output file */ GlhLineNode *node; /* The line being saved */ GlhLineNode *head; /* The head of the list of lines to be saved */ GlhLineSeg *seg; /* One segment of a line being saved */ /* * Check the arguments. */ if(!glh || !filename || !comment) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Attempt to open the specified file. */ fp = fopen(filename, "w"); if(!fp) return _glh_cant_save_history(glh, "Can't open", filename, NULL); /* * If a ceiling on the number of lines to save was specified, count * that number of lines backwards, to find the first line to be saved. */ head = NULL; if(max_lines >= 0) { for(head=glh->list.tail; head && --max_lines > 0; head=head->prev) ; }; if(!head) head = glh->list.head; /* * Write the contents of the history buffer to the history file, writing * associated data such as timestamps, to a line starting with the * specified comment string. */ for(node=head; node; node=node->next) { /* * Write peripheral information associated with the line, as a comment. */ if(fprintf(fp, "%s ", comment) < 0 || _glh_write_timestamp(fp, node->timestamp) || fprintf(fp, " %u\n", node->group) < 0) { return _glh_cant_save_history(glh, "Error writing", filename, fp); }; /* * Write the history line. */ for(seg=node->line->head; seg; seg=seg->next) { size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s); if(fwrite(seg->s, sizeof(char), slen, fp) != slen) return _glh_cant_save_history(glh, "Error writing", filename, fp); }; fputc('\n', fp); }; /* * Close the history file. */ if(fclose(fp) == EOF) return _glh_cant_save_history(glh, "Error writing", filename, NULL); return 0; #endif } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * This is a private error return function of _glh_save_history(). It * composes an error report in the error buffer, composed using * sprintf("%s %s (%s)", message, filename, strerror(errno)). It then * closes fp and returns the error return code of _glh_save_history(). * * Input: * glh GlHistory * The input-line history maintenance object. * message const char * A message to be followed by the filename. * filename const char * The name of the offending output file. * fp FILE * The stream to be closed (send NULL if not * open). * Output: * return int Always 1. */ static int _glh_cant_save_history(GlHistory *glh, const char *message, const char *filename, FILE *fp) { _err_record_msg(glh->err, message, filename, " (", strerror(errno), ")", END_ERR_MSG); if(fp) (void) fclose(fp); return 1; } /*....................................................................... * Write a timestamp to a given stdio stream, in the format * yyyymmddhhmmss * * Input: * fp FILE * The stream to write to. * timestamp time_t The timestamp to be written. * Output: * return int 0 - OK. * 1 - Error. */ static int _glh_write_timestamp(FILE *fp, time_t timestamp) { struct tm *t; /* THe broken-down calendar time */ /* * Get the calendar components corresponding to the given timestamp. */ if(timestamp < 0 || (t = localtime(×tamp)) == NULL) { if(fprintf(fp, "?") < 0) return 1; return 0; }; /* * Write the calendar time as yyyymmddhhmmss. */ if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0) return 1; return 0; } #endif /*....................................................................... * Restore previous history lines from a given file. * * Input: * glh GlHistory * The input-line history maintenance object. * filename const char * The name of the file to read from. * comment const char * The same comment string that was passed to * _glh_save_history() when this file was * written. * line char * A buffer into which lines can be read. * dim size_t The allocated dimension of line[]. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_load_history(GlHistory *glh, const char *filename, const char *comment, char *line, size_t dim) { #ifdef WITHOUT_FILE_SYSTEM _err_record_msg(glh->err, "Can't load history without filesystem access", END_ERR_MSG); errno = EINVAL; return 1; #else FILE *fp; /* The output file */ size_t comment_len; /* The length of the comment string */ time_t timestamp; /* The timestamp of the history line */ unsigned group; /* The identifier of the history group to which */ /* the line belongs. */ int lineno; /* The line number being read */ /* * Check the arguments. */ if(!glh || !filename || !comment || !line) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Measure the length of the comment string. */ comment_len = strlen(comment); /* * Clear the history list. */ _glh_clear_history(glh, 1); /* * Attempt to open the specified file. Don't treat it as an error * if the file doesn't exist. */ fp = fopen(filename, "r"); if(!fp) return 0; /* * Attempt to read each line and preceding peripheral info, and add these * to the history list. */ for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) { char *lptr; /* A pointer into the input line */ /* * Check that the line starts with the comment string. */ if(strncmp(line, comment, comment_len) != 0) { return _glh_cant_load_history(glh, filename, lineno, "Corrupt history parameter line", fp); }; /* * Skip spaces and tabs after the comment. */ for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++) ; /* * The next word must be a timestamp. */ if(_glh_decode_timestamp(lptr, &lptr, ×tamp)) { return _glh_cant_load_history(glh, filename, lineno, "Corrupt timestamp", fp); }; /* * Skip spaces and tabs. */ while(*lptr==' ' || *lptr=='\t') lptr++; /* * The next word must be an unsigned integer group number. */ group = (int) strtoul(lptr, &lptr, 10); if(*lptr != ' ' && *lptr != '\n') { return _glh_cant_load_history(glh, filename, lineno, "Corrupt group id", fp); }; /* * Skip spaces and tabs. */ while(*lptr==' ' || *lptr=='\t') lptr++; /* * There shouldn't be anything left on the line. */ if(*lptr != '\n') { return _glh_cant_load_history(glh, filename, lineno, "Corrupt parameter line", fp); }; /* * Now read the history line itself. */ lineno++; if(fgets(line, dim, fp) == NULL) return _glh_cant_load_history(glh, filename, lineno, "Read error", fp); /* * Append the line to the history buffer. */ if(_glh_add_history(glh, line, 1)) { return _glh_cant_load_history(glh, filename, lineno, "Insufficient memory to record line", fp); }; /* * Record the group and timestamp information along with the line. */ if(glh->list.tail) { glh->list.tail->timestamp = timestamp; glh->list.tail->group = group; }; }; /* * Close the file. */ (void) fclose(fp); return 0; #endif } #ifndef WITHOUT_FILE_SYSTEM /*....................................................................... * This is a private error return function of _glh_load_history(). */ static int _glh_cant_load_history(GlHistory *glh, const char *filename, int lineno, const char *message, FILE *fp) { char lnum[20]; /* * Convert the line number to a string. */ snprintf(lnum, sizeof(lnum), "%d", lineno); /* * Render an error message. */ _err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG); /* * Close the file. */ if(fp) (void) fclose(fp); return 1; } /*....................................................................... * Read a timestamp from a string. * * Input: * string char * The string to read from. * Input/Output: * endp char ** On output *endp will point to the next unprocessed * character in string[]. * timestamp time_t * The timestamp will be assigned to *t. * Output: * return int 0 - OK. * 1 - Error. */ static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp) { unsigned year,month,day,hour,min,sec; /* Calendar time components */ struct tm t; /* * There are 14 characters in the date format yyyymmddhhmmss. */ enum {TSLEN=14}; char timestr[TSLEN+1]; /* The timestamp part of the string */ /* * If the time wasn't available at the time that the line was recorded * it will have been written as "?". Check for this before trying * to read the timestamp. */ if(string[0] == '\?') { *endp = string+1; *timestamp = -1; return 0; }; /* * The timestamp is expected to be written in the form yyyymmddhhmmss. */ if(strlen(string) < TSLEN) { *endp = string; return 1; }; /* * Copy the timestamp out of the string. */ strncpy(timestr, string, TSLEN); timestr[TSLEN] = '\0'; /* * Decode the timestamp. */ if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min, &sec) != 6) { *endp = string; return 1; }; /* * Advance the string pointer over the successfully read timestamp. */ *endp = string + TSLEN; /* * Copy the read values into a struct tm. */ t.tm_sec = sec; t.tm_min = min; t.tm_hour = hour; t.tm_mday = day; t.tm_wday = 0; t.tm_yday = 0; t.tm_mon = month - 1; t.tm_year = year - 1900; t.tm_isdst = -1; /* * Convert the contents of the struct tm to a time_t. */ *timestamp = mktime(&t); return 0; } #endif /*....................................................................... * Switch history groups. * * Input: * glh GlHistory * The input-line history maintenance object. * group unsigned The new group identifier. This will be recorded * with subsequent history lines, and subsequent * history searches will only return lines with * this group identifier. This allows multiple * separate history lists to exist within * a single GlHistory object. Note that the * default group identifier is 0. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_set_group(GlHistory *glh, unsigned group) { /* * Check the arguments. */ if(!glh) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Is the group being changed? */ if(group != glh->group) { /* * Cancel any ongoing search. */ if(_glh_cancel_search(glh)) return 1; /* * Record the new group. */ glh->group = group; }; return 0; } /*....................................................................... * Query the current history group. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return unsigned The group identifier. */ int _glh_get_group(GlHistory *glh) { return glh ? glh->group : 0; } /*....................................................................... * Display the contents of the history list. * * Input: * glh GlHistory * The input-line history maintenance object. * write_fn GlWriteFn * The function to call to write the line, or * 0 to discard the output. * data void * Anonymous data to pass to write_fn(). * fmt const char * A format string. This can contain arbitrary * characters, which are written verbatim, plus * any of the following format directives: * %D - The date, like 2001-11-20 * %T - The time of day, like 23:59:59 * %N - The sequential entry number of the * line in the history buffer. * %G - The history group number of the line. * %% - A literal % character. * %H - The history line. * all_groups int If true, display history lines from all * history groups. Otherwise only display * those of the current history group. * max_lines int If max_lines is < 0, all available lines * are displayed. Otherwise only the most * recent max_lines lines will be displayed. * Output: * return int 0 - OK. * 1 - Error. */ int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data, const char *fmt, int all_groups, int max_lines) { GlhLineNode *node; /* The line being displayed */ GlhLineNode *oldest; /* The oldest line to display */ GlhLineSeg *seg; /* One segment of a line being displayed */ enum {TSMAX=32}; /* The maximum length of the date and time string */ char buffer[TSMAX+1]; /* The buffer in which to write the date and time */ int idlen; /* The length of displayed ID strings */ unsigned grpmax; /* The maximum group number in the buffer */ int grplen; /* The number of characters needed to print grpmax */ int len; /* The length of a string to be written */ /* * Check the arguments. */ if(!glh || !write_fn || !fmt) { if(glh) _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); errno = EINVAL; return 1; }; /* * Is history enabled? */ if(!glh->enable || !glh->list.head) return 0; /* * Work out the length to display ID numbers, choosing the length of * the biggest number in the buffer. Smaller numbers will be padded * with leading zeroes if needed. */ snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) glh->list.tail->id); idlen = strlen(buffer); /* * Find the largest group number. */ grpmax = 0; for(node=glh->list.head; node; node=node->next) { if(node->group > grpmax) grpmax = node->group; }; /* * Find out how many characters are needed to display the group number. */ snprintf(buffer, sizeof(buffer), "%u", (unsigned) grpmax); grplen = strlen(buffer); /* * Find the node that follows the oldest line to be displayed. */ if(max_lines < 0) { oldest = glh->list.head; } else if(max_lines==0) { return 0; } else { for(oldest=glh->list.tail; oldest; oldest=oldest->prev) { if((all_groups || oldest->group == glh->group) && --max_lines <= 0) break; }; /* * If the number of lines in the buffer doesn't exceed the specified * maximum, start from the oldest line in the buffer. */ if(!oldest) oldest = glh->list.head; }; /* * List the history lines in increasing time order. */ for(node=oldest; node; node=node->next) { /* * Only display lines from the current history group, unless * told otherwise. */ if(all_groups || node->group == glh->group) { const char *fptr; /* A pointer into the format string */ struct tm *t = NULL; /* The broken time version of the timestamp */ /* * Work out the calendar representation of the node timestamp. */ if(node->timestamp != (time_t) -1) t = localtime(&node->timestamp); /* * Parse the format string. */ fptr = fmt; while(*fptr) { /* * Search for the start of the next format directive or the end of the string. */ const char *start = fptr; while(*fptr && *fptr != '%') fptr++; /* * Display any literal characters that precede the located directive. */ if(fptr > start) { len = (int) (fptr - start); if(write_fn(data, start, len) != len) return 1; }; /* * Did we hit a new directive before the end of the line? */ if(*fptr) { /* * Obey the directive. Ignore unknown directives. */ switch(*++fptr) { case 'D': /* Display the date */ if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) { len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; }; break; case 'T': /* Display the time of day */ if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) { len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; }; break; case 'N': /* Display the sequential entry number */ snprintf(buffer, sizeof(buffer), "%*lu", idlen, (unsigned long) node->id); len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; break; case 'G': snprintf(buffer, sizeof(buffer), "%*u", grplen, (unsigned) node->group); len = strlen(buffer); if(write_fn(data, buffer, len) != len) return 1; break; case 'H': /* Display the history line */ for(seg=node->line->head; seg; seg=seg->next) { len = seg->next ? GLH_SEG_SIZE : strlen(seg->s); if(write_fn(data, seg->s, len) != len) return 1; }; break; case '%': /* A literal % symbol */ if(write_fn(data, "%", 1) != 1) return 1; break; }; /* * Skip the directive. */ if(*fptr) fptr++; }; }; }; }; return 0; } /*....................................................................... * Change the size of the history buffer. * * Input: * glh GlHistory * The input-line history maintenance object. * bufsize size_t The number of bytes in the history buffer, or 0 * to delete the buffer completely. * Output: * return int 0 - OK. * 1 - Insufficient memory (the previous buffer * will have been retained). No error message * will be displayed. */ int _glh_resize_history(GlHistory *glh, size_t bufsize) { int nbuff; /* The number of segments in the new buffer */ int i; /* * Check the arguments. */ if(!glh) { errno = EINVAL; return 1; }; /* * How many buffer segments does the requested buffer size correspond * to? */ nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; /* * Has a different size than the current size been requested? */ if(glh->nbuff != nbuff) { /* * Cancel any ongoing search. */ (void) _glh_cancel_search(glh); /* * Create a wholly new buffer? */ if(glh->nbuff == 0 && nbuff>0) { glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff); if(!glh->buffer) return 1; glh->nbuff = nbuff; glh->nfree = glh->nbuff; glh->nbusy = 0; glh->nline = 0; /* * Link the currently unused nodes of the buffer into a list. */ glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; /* * Delete an existing buffer? */ } else if(nbuff == 0) { _glh_clear_history(glh, 1); free(glh->buffer); glh->buffer = NULL; glh->unused = NULL; glh->nbuff = 0; glh->nfree = 0; glh->nbusy = 0; glh->nline = 0; /* * Change from one finite buffer size to another? */ } else { GlhLineSeg *buffer; /* The resized buffer */ int nbusy; /* The number of used line segments in the new buffer */ /* * Starting from the oldest line in the buffer, discard lines until * the buffer contains at most 'nbuff' used line segments. */ while(glh->list.head && glh->nbusy > nbuff) _glh_discard_line(glh, glh->list.head); /* * Attempt to allocate a new buffer. */ buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg)); if(!buffer) { errno = ENOMEM; return 1; }; /* * Copy the used segments of the old buffer to the start of the new buffer. */ nbusy = 0; for(i=0; ihash.bucket + i; GlhHashNode *hnode; for(hnode=b->lines; hnode; hnode=hnode->next) { GlhLineSeg *seg = hnode->head; hnode->head = buffer + nbusy; for( ; seg; seg=seg->next) { buffer[nbusy] = *seg; buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL; nbusy++; }; }; }; /* * Make a list of the new buffer's unused segments. */ for(i=nbusy; ibuffer); /* * Install the new buffer. */ glh->buffer = buffer; glh->nbuff = nbuff; glh->nbusy = nbusy; glh->nfree = nbuff - nbusy; glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL; }; }; return 0; } /*....................................................................... * Set an upper limit to the number of lines that can be recorded in the * history list, or remove a previously specified limit. * * Input: * glh GlHistory * The input-line history maintenance object. * max_lines int The maximum number of lines to allow, or -1 to * cancel a previous limit and allow as many lines * as will fit in the current history buffer size. */ void _glh_limit_history(GlHistory *glh, int max_lines) { if(!glh) return; /* * Apply a new limit? */ if(max_lines >= 0 && max_lines != glh->max_lines) { /* * Count successively older lines until we reach the start of the * list, or until we have seen max_lines lines (at which point 'node' * will be line number max_lines+1). */ int nline = 0; GlhLineNode *node; for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev) ; /* * Discard any lines that exceed the limit. */ if(node) { GlhLineNode *oldest = node->next; /* The oldest line to be kept */ /* * Delete nodes from the head of the list until we reach the node that * is to be kept. */ while(glh->list.head && glh->list.head != oldest) _glh_discard_line(glh, glh->list.head); }; }; /* * Record the new limit. */ glh->max_lines = max_lines; return; } /*....................................................................... * Discard either all history, or the history associated with the current * history group. * * Input: * glh GlHistory * The input-line history maintenance object. * all_groups int If true, clear all of the history. If false, * clear only the stored lines associated with the * currently selected history group. */ void _glh_clear_history(GlHistory *glh, int all_groups) { int i; /* * Check the arguments. */ if(!glh) return; /* * Cancel any ongoing search. */ (void) _glh_cancel_search(glh); /* * Delete all history lines regardless of group? */ if(all_groups) { /* * Claer the time-ordered list of lines. */ _rst_FreeList(glh->list.node_mem); glh->list.head = glh->list.tail = NULL; glh->nline = 0; glh->id_node = NULL; /* * Clear the hash table. */ for(i=0; ihash.bucket[i].lines = NULL; _rst_FreeList(glh->hash.node_mem); /* * Move all line segment nodes back onto the list of unused segments. */ if(glh->buffer) { glh->unused = glh->buffer; for(i=0; inbuff-1; i++) { GlhLineSeg *seg = glh->unused + i; seg->next = seg + 1; }; glh->unused[i].next = NULL; glh->nfree = glh->nbuff; glh->nbusy = 0; } else { glh->unused = NULL; glh->nbusy = glh->nfree = 0; }; /* * Just delete lines of the current group? */ } else { GlhLineNode *node; /* The line node being checked */ GlhLineNode *next; /* The line node that follows 'node' */ /* * Search out and delete the line nodes of the current group. */ for(node=glh->list.head; node; node=next) { /* * Keep a record of the following node before we delete the current * node. */ next = node->next; /* * Discard this node? */ if(node->group == glh->group) _glh_discard_line(glh, node); }; }; return; } /*....................................................................... * Temporarily enable or disable the history list. * * Input: * glh GlHistory * The input-line history maintenance object. * enable int If true, turn on the history mechanism. If * false, disable it. */ void _glh_toggle_history(GlHistory *glh, int enable) { if(glh) glh->enable = enable; } /*....................................................................... * Discard a given archived input line. * * Input: * glh GlHistory * The history container object. * node GlhLineNode * The line to be discarded, specified via its * entry in the time-ordered list of historical * input lines. */ static void _glh_discard_line(GlHistory *glh, GlhLineNode *node) { /* * Remove the node from the linked list. */ if(node->prev) node->prev->next = node->next; else glh->list.head = node->next; if(node->next) node->next->prev = node->prev; else glh->list.tail = node->prev; /* * If we are deleting the node that is marked as the start point of the * last ID search, remove the cached starting point. */ if(node == glh->id_node) glh->id_node = NULL; /* * If we are deleting the node that is marked as the start point of the * next prefix search, cancel the search. */ if(node == glh->recall) _glh_cancel_search(glh); /* * Delete our copy of the line. */ node->line = _glh_discard_copy(glh, node->line); /* * Return the node to the freelist. */ (void) _del_FreeListNode(glh->list.node_mem, node); /* * Record the removal of a line from the list. */ glh->nline--; return; } /*....................................................................... * Lookup the details of a given history line, given its id. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlLineID The sequential number of the line. * Input/Output: * line const char ** A pointer to a copy of the history line will be * assigned to *line. Beware that this pointer may * be invalidated by the next call to any public * history function. * group unsigned * The group membership of the line will be assigned * to *group. * timestamp time_t * The timestamp of the line will be assigned to * *timestamp. * Output: * return int 0 - The requested line wasn't found. * 1 - The line was found. */ int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line, unsigned *group, time_t *timestamp) { GlhLineNode *node; /* The located line location node */ /* * Check the arguments. */ if(!glh) return 0; /* * Search for the line that has the specified ID. */ node = _glh_find_id(glh, id); /* * Not found? */ if(!node) return 0; /* * Has the history line been requested? */ if(line) { /* * If necessary, reallocate the lookup buffer to accomodate the size of * a copy of the located line. */ if(node->line->len + 1 > glh->lbuf_dim) { int lbuf_dim = node->line->len + 1; char *lbuf = realloc(glh->lbuf, lbuf_dim); if(!lbuf) { errno = ENOMEM; return 0; }; glh->lbuf_dim = lbuf_dim; glh->lbuf = lbuf; }; /* * Copy the history line into the lookup buffer. */ _glh_return_line(node->line, glh->lbuf, glh->lbuf_dim); /* * Assign the lookup buffer as the returned line pointer. */ *line = glh->lbuf; }; /* * Does the caller want to know the group of the line? */ if(group) *group = node->group; /* * Does the caller want to know the timestamp of the line? */ if(timestamp) *timestamp = node->timestamp; return 1; } /*....................................................................... * Lookup a node in the history list by its ID. * * Input: * glh GlHistory * The input-line history maintenance object. * id GlhLineID The ID of the line to be returned. * Output: * return GlhLIneNode * The located node, or NULL if not found. */ static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id) { GlhLineNode *node; /* The node being checked */ /* * Is history enabled? */ if(!glh->enable || !glh->list.head) return NULL; /* * If possible, start at the end point of the last ID search. * Otherwise start from the head of the list. */ node = glh->id_node; if(!node) node = glh->list.head; /* * Search forwards from 'node'? */ if(node->id < id) { while(node && node->id != id) node = node->next; glh->id_node = node ? node : glh->list.tail; /* * Search backwards from 'node'? */ } else { while(node && node->id != id) node = node->prev; glh->id_node = node ? node : glh->list.head; }; /* * Return the located node (this will be NULL if the ID wasn't found). */ return node; } /*....................................................................... * Query the state of the history list. Note that any of the input/output * pointers can be specified as NULL. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * enabled int * If history is enabled, *enabled will be * set to 1. Otherwise it will be assigned 0. * group unsigned * The current history group ID will be assigned * to *group. * max_lines int * The currently requested limit on the number * of history lines in the list, or -1 if * unlimited. */ void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group, int *max_lines) { if(glh) { if(enabled) *enabled = glh->enable; if(group) *group = glh->group; if(max_lines) *max_lines = glh->max_lines; }; } /*....................................................................... * Get the range of lines in the history buffer. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * oldest unsigned long * The sequential entry number of the oldest * line in the history list will be assigned * to *oldest, unless there are no lines, in * which case 0 will be assigned. * newest unsigned long * The sequential entry number of the newest * line in the history list will be assigned * to *newest, unless there are no lines, in * which case 0 will be assigned. * nlines int * The number of lines currently in the history * list. */ void _glh_range_of_history(GlHistory *glh, unsigned long *oldest, unsigned long *newest, int *nlines) { if(glh) { if(oldest) *oldest = glh->list.head ? glh->list.head->id : 0; if(newest) *newest = glh->list.tail ? glh->list.tail->id : 0; if(nlines) *nlines = glh->nline; }; } /*....................................................................... * Return the size of the history buffer and the amount of the * buffer that is currently in use. * * Input: * glh GlHistory * The input-line history maintenance object. * Input/Output: * buff_size size_t * The size of the history buffer (bytes). * buff_used size_t * The amount of the history buffer that * is currently occupied (bytes). */ void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used) { if(glh) { if(buff_size) *buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE; /* * Determine the amount of buffer space that is currently occupied. */ if(buff_used) *buff_used = glh->nbusy * GLH_SEG_SIZE; }; } /*....................................................................... * Return extra information (ie. in addition to that provided by errno) * about the last error to occur in any of the public functions of this * module. * * Input: * glh GlHistory * The container of the history list. * Output: * return const char * A pointer to the internal buffer in which * the error message is temporarily stored. */ const char *_glh_last_error(GlHistory *glh) { return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument"; } /*....................................................................... * Unless already stored, store a copy of the line in the history buffer, * then return a reference-counted hash-node pointer to this copy. * * Input: * glh GlHistory * The history maintenance buffer. * line const char * The history line to be recorded. * n size_t The length of the string, excluding any '\0' * terminator. * Output: * return GlhHashNode * The hash-node containing the stored line, or * NULL on error. */ static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, size_t n) { GlhHashBucket *bucket; /* The hash-table bucket of the line */ GlhHashNode *hnode; /* The hash-table node of the line */ int i; /* * In which bucket should the line be recorded? */ bucket = glh_find_bucket(glh, line, n); /* * Is the line already recorded there? */ hnode = glh_find_hash_node(bucket, line, n); /* * If the line isn't recorded in the buffer yet, make room for it. */ if(!hnode) { GlhLineSeg *seg; /* A line segment */ int offset; /* An offset into line[] */ /* * How many string segments will be needed to record the new line, * including space for a '\0' terminator? */ int nseg = ((n+1) + GLH_SEG_SIZE-1) / GLH_SEG_SIZE; /* * Discard the oldest history lines in the buffer until at least * 'nseg' segments have been freed up, or until we run out of buffer * space. */ while(glh->nfree < nseg && glh->nbusy > 0) _glh_discard_line(glh, glh->list.head); /* * If the buffer is smaller than the new line, don't attempt to truncate * it to fit. Simply don't archive it. */ if(glh->nfree < nseg) return NULL; /* * Record the line in the first 'nseg' segments of the list of unused segments. */ offset = 0; for(i=0,seg=glh->unused; inext, offset+=GLH_SEG_SIZE) memcpy(seg->s, line + offset, GLH_SEG_SIZE); memcpy(seg->s, line + offset, n-offset); seg->s[n-offset] = '\0'; /* * Create a new hash-node for the line. */ hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem); if(!hnode) return NULL; /* * Move the copy of the line from the list of unused segments to * the hash node. */ hnode->head = glh->unused; glh->unused = seg->next; seg->next = NULL; glh->nbusy += nseg; glh->nfree -= nseg; /* * Prepend the new hash node to the list within the associated bucket. */ hnode->next = bucket->lines; bucket->lines = hnode; /* * Initialize the rest of the members of the hash node. */ hnode->len = n; hnode->reported = 0; hnode->used = 0; hnode->bucket = bucket; }; /* * Increment the reference count of the line. */ hnode->used++; return hnode; } /*....................................................................... * Decrement the reference count of the history line of a given hash-node, * and if the count reaches zero, delete both the hash-node and the * buffered copy of the line. * * Input: * glh GlHistory * The history container object. * hnode GlhHashNode * The node to be removed. * Output: * return GlhHashNode * The deleted hash-node (ie. NULL). */ static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode) { if(hnode) { GlhHashBucket *bucket = hnode->bucket; /* * If decrementing the reference count of the hash-node doesn't reduce * the reference count to zero, then the line is still in use in another * object, so don't delete it yet. Return NULL to indicate that the caller's * access to the hash-node copy has been deleted. */ if(--hnode->used >= 1) return NULL; /* * Remove the hash-node from the list in its parent bucket. */ if(bucket->lines == hnode) { bucket->lines = hnode->next; } else { GlhHashNode *prev; /* The node which precedes hnode in the bucket */ for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next) ; if(prev) prev->next = hnode->next; }; hnode->next = NULL; /* * Return the line segments of the hash-node to the list of unused segments. */ if(hnode->head) { GlhLineSeg *tail; /* The last node in the list of line segments */ int nseg; /* The number of segments being discarded */ /* * Get the last node of the list of line segments referenced in the hash-node, * while counting the number of line segments used. */ for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next) ; /* * Prepend the list of line segments used by the hash node to the * list of unused line segments. */ tail->next = glh->unused; glh->unused = hnode->head; glh->nbusy -= nseg; glh->nfree += nseg; }; /* * Return the container of the hash-node to the freelist. */ hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode); }; return NULL; } /*....................................................................... * Private function to locate the hash bucket associated with a given * history line. * * This uses a hash-function described in the dragon-book * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and * Ullman; pub. Adison Wesley) page 435. * * Input: * glh GlHistory * The history container object. * line const char * The historical line to look up. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return GlhHashBucket * The located hash-bucket. */ static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, size_t n) { unsigned long h = 0L; int i; for(i=0; ihash.bucket + (h % GLH_HASH_SIZE); } /*....................................................................... * Find a given history line within a given hash-table bucket. * * Input: * bucket GlhHashBucket * The hash-table bucket in which to search. * line const char * The historical line to lookup. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return GlhHashNode * The hash-table entry of the line, or NULL * if not found. */ static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, size_t n) { GlhHashNode *node; /* A node in the list of lines in the bucket */ /* * Compare each of the lines in the list of lines, against 'line'. */ for(node=bucket->lines; node; node=node->next) { if(_glh_is_line(node, line, n)) return node; }; return NULL; } /*....................................................................... * Return non-zero if a given string is equal to a given segmented line * node. * * Input: * hash GlhHashNode * The hash-table entry of the line. * line const char * The string to be compared to the segmented * line. * n size_t The length of the line in line[], excluding * any '\0' terminator. * Output: * return int 0 - The lines differ. * 1 - The lines are the same. */ static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n) { GlhLineSeg *seg; /* A node in the list of line segments */ int i; /* * Do the two lines have the same length? */ if(n != hash->len) return 0; /* * Compare the characters of the segmented and unsegmented versions * of the line. */ for(seg=hash->head; n>0 && seg; seg=seg->next) { const char *s = seg->s; for(i=0; n>0 && ilen > line->len) return 0; /* * Compare the line to the prefix. */ while(pstr.c != '\0' && pstr.c == lstr.c) { glh_step_stream(&lstr); glh_step_stream(&pstr); }; /* * Did we reach the end of the prefix string before finding * any differences? */ return pstr.c == '\0'; } /*....................................................................... * Copy a given history line into a specified output string. * * Input: * hash GlhHashNode The hash-table entry of the history line to * be copied. * line char * A copy of the history line. * dim size_t The allocated dimension of the line buffer. */ static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim) { GlhLineSeg *seg; /* A node in the list of line segments */ int i; for(seg=hash->head; dim>0 && seg; seg=seg->next) { const char *s = seg->s; for(i=0; dim>0 && irecall && glh->recall == glh->list.tail && !_glh_is_line(glh->recall->line, line, strlen(line))) { _glh_cancel_search(glh); }; /* * If this is the first line recall of a new recall session, save the * current line for potential recall later, and mark it as the last * line recalled. */ if(!glh->recall) { if(_glh_add_history(glh, line, 1)) return 1; glh->recall = glh->list.tail; /* * The above call to _glh_add_history() will have incremented the line * sequence number, after adding the line. Since we only want this to * to be incremented for permanently entered lines, decrement it again. */ glh->seq--; }; return 0; } /*....................................................................... * Return non-zero if a history search session is currently in progress. * * Input: * glh GlHistory * The input-line history maintenance object. * Output: * return int 0 - No search is currently in progress. * 1 - A search is in progress. */ int _glh_search_active(GlHistory *glh) { return glh && glh->recall; } /*....................................................................... * Initialize a character iterator object to point to the start of a * given history line. The first character of the line will be placed * in str->c, and subsequent characters can be placed there by calling * glh_strep_stream(). * * Input: * str GlhLineStream * The iterator object to be initialized. * line GlhHashNode * The history line to be iterated over (a * NULL value here, is interpretted as an * empty string by glh_step_stream()). */ static void glh_init_stream(GlhLineStream *str, GlhHashNode *line) { str->seg = line ? line->head : NULL; str->posn = 0; str->c = str->seg ? str->seg->s[0] : '\0'; } /*....................................................................... * Copy the next unread character in the line being iterated, in str->c. * Once the end of the history line has been reached, all futher calls * set str->c to '\0'. * * Input: * str GlhLineStream * The history-line iterator to read from. */ static void glh_step_stream(GlhLineStream *str) { /* * Get the character from the current iterator position within the line. */ str->c = str->seg ? str->seg->s[str->posn] : '\0'; /* * Unless we have reached the end of the string, move the iterator * to the position of the next character in the line. */ if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) { str->posn = 0; str->seg = str->seg->next; }; } /*....................................................................... * Return non-zero if the specified search prefix contains any glob * wildcard characters. * * Input: * prefix GlhHashNode * The search prefix. * Output: * return int 0 - The prefix doesn't contain any globbing * characters. * 1 - The prefix contains at least one * globbing character. */ static int glh_contains_glob(GlhHashNode *prefix) { GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ /* * Wrap a stream iterator around the prefix, so that we can traverse it * without worrying about line-segmentation. */ glh_init_stream(&pstr, prefix); /* * Search for unescaped wildcard characters. */ while(pstr.c != '\0') { switch(pstr.c) { case '\\': /* Skip escaped characters */ glh_step_stream(&pstr); break; case '*': case '?': case '[': /* A wildcard character? */ return 1; break; }; glh_step_stream(&pstr); }; /* * No wildcard characters were found. */ return 0; } /*....................................................................... * Return non-zero if the history line matches a search prefix containing * a glob pattern. * * Input: * lstr GlhLineStream * The iterator stream being used to traverse * the history line that is being matched. * pstr GlhLineStream * The iterator stream being used to traverse * the pattern. * Output: * return int 0 - Doesn't match. * 1 - The line matches the pattern. */ static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr) { /* * Match each character of the pattern until we reach the end of the * pattern. */ while(pstr->c != '\0') { /* * Handle the next character of the pattern. */ switch(pstr->c) { /* * A match zero-or-more characters wildcard operator. */ case '*': /* * Skip the '*' character in the pattern. */ glh_step_stream(pstr); /* * If the pattern ends with the '*' wildcard, then the * rest of the line matches this. */ if(pstr->c == '\0') return 1; /* * Using the wildcard to match successively longer sections of * the remaining characters of the line, attempt to match * the tail of the line against the tail of the pattern. */ while(lstr->c) { GlhLineStream old_lstr = *lstr; GlhLineStream old_pstr = *pstr; if(glh_line_matches_glob(lstr, pstr)) return 1; /* * Restore the line and pattern iterators for a new try. */ *lstr = old_lstr; *pstr = old_pstr; /* * Prepare to try again, one character further into the line. */ glh_step_stream(lstr); }; return 0; /* The pattern following the '*' didn't match */ break; /* * A match-one-character wildcard operator. */ case '?': /* * If there is a character to be matched, skip it and advance the * pattern pointer. */ if(lstr->c) { glh_step_stream(lstr); glh_step_stream(pstr); /* * If we hit the end of the line, there is no character * matching the operator, so the pattern doesn't match. */ } else { return 0; }; break; /* * A character range operator, with the character ranges enclosed * in matching square brackets. */ case '[': glh_step_stream(pstr); /* Skip the '[' character */ if(!lstr->c || !glh_matches_range(lstr->c, pstr)) return 0; glh_step_stream(lstr); /* Skip the character that matched */ break; /* * A backslash in the pattern prevents the following character as * being seen as a special character. */ case '\\': glh_step_stream(pstr); /* Skip the backslash */ /* Note fallthrough to default */ /* * A normal character to be matched explicitly. */ /* FALLTHROUGH */ default: if(lstr->c == pstr->c) { glh_step_stream(lstr); glh_step_stream(pstr); } else { return 0; }; break; }; }; /* * To get here, pattern must have been exhausted. The line only * matches the pattern if the line as also been exhausted. */ return pstr->c == '\0' && lstr->c == '\0'; } /*....................................................................... * Match a character range expression terminated by an unescaped close * square bracket. * * Input: * c char The character to be matched with the range * pattern. * pstr GlhLineStream * The iterator stream being used to traverse * the pattern. * Output: * return int 0 - Doesn't match. * 1 - The character matched. */ static int glh_matches_range(char c, GlhLineStream *pstr) { int invert = 0; /* True to invert the sense of the match */ int matched = 0; /* True if the character matched the pattern */ char lastc = '\0'; /* The previous character in the pattern */ /* * If the first character is a caret, the sense of the match is * inverted and only if the character isn't one of those in the * range, do we say that it matches. */ if(pstr->c == '^') { glh_step_stream(pstr); invert = 1; }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret). */ if(pstr->c == '-') { glh_step_stream(pstr); if(c == '-') matched = 1; /* * Skip other leading '-' characters since they make no sense. */ while(pstr->c == '-') glh_step_stream(pstr); }; /* * The hyphen is only a special character when it follows the first * character of the range (not including the caret or a hyphen). */ if(pstr->c == ']') { glh_step_stream(pstr); if(c == ']') matched = 1; }; /* * Having dealt with the characters that have special meanings at * the beginning of a character range expression, see if the * character matches any of the remaining characters of the range, * up until a terminating ']' character is seen. */ while(!matched && pstr->c && pstr->c != ']') { /* * Is this a range of characters signaled by the two end characters * separated by a hyphen? */ if(pstr->c == '-') { glh_step_stream(pstr); /* Skip the hyphen */ if(pstr->c != ']') { if(c >= lastc && c <= pstr->c) matched = 1; }; /* * A normal character to be compared directly. */ } else if(pstr->c == c) { matched = 1; }; /* * Record and skip the character that we just processed. */ lastc = pstr->c; if(pstr->c != ']') glh_step_stream(pstr); }; /* * Find the terminating ']'. */ while(pstr->c && pstr->c != ']') glh_step_stream(pstr); /* * Did we find a terminating ']'? */ if(pstr->c == ']') { /* * Skip the terminating ']'. */ glh_step_stream(pstr); /* * If the pattern started with a caret, invert the sense of the match. */ if(invert) matched = !matched; /* * If the pattern didn't end with a ']', then it doesn't match, * regardless of the value of the required sense of the match. */ } else { matched = 0; }; return matched; }