1*7c478bd9Sstevel@tonic-gate /* 2*7c478bd9Sstevel@tonic-gate * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. 3*7c478bd9Sstevel@tonic-gate * 4*7c478bd9Sstevel@tonic-gate * All rights reserved. 5*7c478bd9Sstevel@tonic-gate * 6*7c478bd9Sstevel@tonic-gate * Permission is hereby granted, free of charge, to any person obtaining a 7*7c478bd9Sstevel@tonic-gate * copy of this software and associated documentation files (the 8*7c478bd9Sstevel@tonic-gate * "Software"), to deal in the Software without restriction, including 9*7c478bd9Sstevel@tonic-gate * without limitation the rights to use, copy, modify, merge, publish, 10*7c478bd9Sstevel@tonic-gate * distribute, and/or sell copies of the Software, and to permit persons 11*7c478bd9Sstevel@tonic-gate * to whom the Software is furnished to do so, provided that the above 12*7c478bd9Sstevel@tonic-gate * copyright notice(s) and this permission notice appear in all copies of 13*7c478bd9Sstevel@tonic-gate * the Software and that both the above copyright notice(s) and this 14*7c478bd9Sstevel@tonic-gate * permission notice appear in supporting documentation. 15*7c478bd9Sstevel@tonic-gate * 16*7c478bd9Sstevel@tonic-gate * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17*7c478bd9Sstevel@tonic-gate * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18*7c478bd9Sstevel@tonic-gate * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 19*7c478bd9Sstevel@tonic-gate * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20*7c478bd9Sstevel@tonic-gate * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 21*7c478bd9Sstevel@tonic-gate * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING 22*7c478bd9Sstevel@tonic-gate * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 23*7c478bd9Sstevel@tonic-gate * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 24*7c478bd9Sstevel@tonic-gate * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25*7c478bd9Sstevel@tonic-gate * 26*7c478bd9Sstevel@tonic-gate * Except as contained in this notice, the name of a copyright holder 27*7c478bd9Sstevel@tonic-gate * shall not be used in advertising or otherwise to promote the sale, use 28*7c478bd9Sstevel@tonic-gate * or other dealings in this Software without prior written authorization 29*7c478bd9Sstevel@tonic-gate * of the copyright holder. 30*7c478bd9Sstevel@tonic-gate */ 31*7c478bd9Sstevel@tonic-gate 32*7c478bd9Sstevel@tonic-gate /* 33*7c478bd9Sstevel@tonic-gate * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 34*7c478bd9Sstevel@tonic-gate * Use is subject to license terms. 35*7c478bd9Sstevel@tonic-gate */ 36*7c478bd9Sstevel@tonic-gate 37*7c478bd9Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 38*7c478bd9Sstevel@tonic-gate 39*7c478bd9Sstevel@tonic-gate #include <stdlib.h> 40*7c478bd9Sstevel@tonic-gate #include <stdio.h> 41*7c478bd9Sstevel@tonic-gate #include <string.h> 42*7c478bd9Sstevel@tonic-gate #include <ctype.h> 43*7c478bd9Sstevel@tonic-gate #include <time.h> 44*7c478bd9Sstevel@tonic-gate #include <errno.h> 45*7c478bd9Sstevel@tonic-gate 46*7c478bd9Sstevel@tonic-gate #include "ioutil.h" 47*7c478bd9Sstevel@tonic-gate #include "history.h" 48*7c478bd9Sstevel@tonic-gate #include "freelist.h" 49*7c478bd9Sstevel@tonic-gate #include "errmsg.h" 50*7c478bd9Sstevel@tonic-gate 51*7c478bd9Sstevel@tonic-gate /* 52*7c478bd9Sstevel@tonic-gate * History lines are split into sub-strings of GLH_SEG_SIZE 53*7c478bd9Sstevel@tonic-gate * characters. To avoid wasting space in the GlhLineSeg structure, 54*7c478bd9Sstevel@tonic-gate * this should be a multiple of the size of a pointer. 55*7c478bd9Sstevel@tonic-gate */ 56*7c478bd9Sstevel@tonic-gate #define GLH_SEG_SIZE 16 57*7c478bd9Sstevel@tonic-gate 58*7c478bd9Sstevel@tonic-gate /* 59*7c478bd9Sstevel@tonic-gate * GlhLineSeg structures contain fixed sized segments of a larger 60*7c478bd9Sstevel@tonic-gate * string. These are linked into lists to record strings, with all but 61*7c478bd9Sstevel@tonic-gate * the last segment having GLH_SEG_SIZE characters. The last segment 62*7c478bd9Sstevel@tonic-gate * of a string is terminated within the GLH_SEG_SIZE characters with a 63*7c478bd9Sstevel@tonic-gate * '\0'. 64*7c478bd9Sstevel@tonic-gate */ 65*7c478bd9Sstevel@tonic-gate typedef struct GlhLineSeg GlhLineSeg; 66*7c478bd9Sstevel@tonic-gate struct GlhLineSeg { 67*7c478bd9Sstevel@tonic-gate GlhLineSeg *next; /* The next sub-string of the history line */ 68*7c478bd9Sstevel@tonic-gate char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */ 69*7c478bd9Sstevel@tonic-gate /* substring of a line, as indicated by 'next' */ 70*7c478bd9Sstevel@tonic-gate /* being NULL, is '\0' terminated. */ 71*7c478bd9Sstevel@tonic-gate }; 72*7c478bd9Sstevel@tonic-gate 73*7c478bd9Sstevel@tonic-gate /* 74*7c478bd9Sstevel@tonic-gate * History lines are recorded in a hash table, such that repeated 75*7c478bd9Sstevel@tonic-gate * lines are stored just once. 76*7c478bd9Sstevel@tonic-gate * 77*7c478bd9Sstevel@tonic-gate * Start by defining the size of the hash table. This should be a 78*7c478bd9Sstevel@tonic-gate * prime number. 79*7c478bd9Sstevel@tonic-gate */ 80*7c478bd9Sstevel@tonic-gate #define GLH_HASH_SIZE 113 81*7c478bd9Sstevel@tonic-gate 82*7c478bd9Sstevel@tonic-gate typedef struct GlhHashBucket GlhHashBucket; 83*7c478bd9Sstevel@tonic-gate 84*7c478bd9Sstevel@tonic-gate /* 85*7c478bd9Sstevel@tonic-gate * Each history line will be represented in the hash table by a 86*7c478bd9Sstevel@tonic-gate * structure of the following type. 87*7c478bd9Sstevel@tonic-gate */ 88*7c478bd9Sstevel@tonic-gate typedef struct GlhHashNode GlhHashNode; 89*7c478bd9Sstevel@tonic-gate struct GlhHashNode { 90*7c478bd9Sstevel@tonic-gate GlhHashBucket *bucket; /* The parent hash-table bucket of this node */ 91*7c478bd9Sstevel@tonic-gate GlhHashNode *next; /* The next in the list of nodes within the */ 92*7c478bd9Sstevel@tonic-gate /* parent hash-table bucket. */ 93*7c478bd9Sstevel@tonic-gate GlhLineSeg *head; /* The list of sub-strings which make up a line */ 94*7c478bd9Sstevel@tonic-gate int len; /* The length of the line, excluding any '\0' */ 95*7c478bd9Sstevel@tonic-gate int used; /* The number of times this string is pointed to by */ 96*7c478bd9Sstevel@tonic-gate /* the time-ordered list of history lines. */ 97*7c478bd9Sstevel@tonic-gate int reported; /* A flag that is used when searching to ensure that */ 98*7c478bd9Sstevel@tonic-gate /* a line isn't reported redundantly. */ 99*7c478bd9Sstevel@tonic-gate }; 100*7c478bd9Sstevel@tonic-gate 101*7c478bd9Sstevel@tonic-gate /* 102*7c478bd9Sstevel@tonic-gate * How many new GlhHashNode elements should be allocated at a time? 103*7c478bd9Sstevel@tonic-gate */ 104*7c478bd9Sstevel@tonic-gate #define GLH_HASH_INCR 50 105*7c478bd9Sstevel@tonic-gate 106*7c478bd9Sstevel@tonic-gate static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n); 107*7c478bd9Sstevel@tonic-gate static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix); 108*7c478bd9Sstevel@tonic-gate static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim); 109*7c478bd9Sstevel@tonic-gate 110*7c478bd9Sstevel@tonic-gate /* 111*7c478bd9Sstevel@tonic-gate * All history lines which hash to a given bucket in the hash table, are 112*7c478bd9Sstevel@tonic-gate * recorded in a structure of the following type. 113*7c478bd9Sstevel@tonic-gate */ 114*7c478bd9Sstevel@tonic-gate struct GlhHashBucket { 115*7c478bd9Sstevel@tonic-gate GlhHashNode *lines; /* The list of history lines which fall in this bucket */ 116*7c478bd9Sstevel@tonic-gate }; 117*7c478bd9Sstevel@tonic-gate 118*7c478bd9Sstevel@tonic-gate static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, 119*7c478bd9Sstevel@tonic-gate size_t n); 120*7c478bd9Sstevel@tonic-gate static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, 121*7c478bd9Sstevel@tonic-gate size_t n); 122*7c478bd9Sstevel@tonic-gate 123*7c478bd9Sstevel@tonic-gate typedef struct { 124*7c478bd9Sstevel@tonic-gate FreeList *node_mem; /* A free-list of GlhHashNode structures */ 125*7c478bd9Sstevel@tonic-gate GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */ 126*7c478bd9Sstevel@tonic-gate } GlhLineHash; 127*7c478bd9Sstevel@tonic-gate 128*7c478bd9Sstevel@tonic-gate /* 129*7c478bd9Sstevel@tonic-gate * GlhLineNode's are used to record history lines in time order. 130*7c478bd9Sstevel@tonic-gate */ 131*7c478bd9Sstevel@tonic-gate typedef struct GlhLineNode GlhLineNode; 132*7c478bd9Sstevel@tonic-gate struct GlhLineNode { 133*7c478bd9Sstevel@tonic-gate long id; /* The unique identifier of this history line */ 134*7c478bd9Sstevel@tonic-gate time_t timestamp; /* The time at which the line was archived */ 135*7c478bd9Sstevel@tonic-gate unsigned group; /* The identifier of the history group to which the */ 136*7c478bd9Sstevel@tonic-gate /* the line belongs. */ 137*7c478bd9Sstevel@tonic-gate GlhLineNode *next; /* The next youngest line in the list */ 138*7c478bd9Sstevel@tonic-gate GlhLineNode *prev; /* The next oldest line in the list */ 139*7c478bd9Sstevel@tonic-gate GlhHashNode *line; /* The hash-table entry of the history line */ 140*7c478bd9Sstevel@tonic-gate }; 141*7c478bd9Sstevel@tonic-gate 142*7c478bd9Sstevel@tonic-gate /* 143*7c478bd9Sstevel@tonic-gate * The number of GlhLineNode elements per freelist block. 144*7c478bd9Sstevel@tonic-gate */ 145*7c478bd9Sstevel@tonic-gate #define GLH_LINE_INCR 100 146*7c478bd9Sstevel@tonic-gate 147*7c478bd9Sstevel@tonic-gate /* 148*7c478bd9Sstevel@tonic-gate * Encapsulate the time-ordered list of historical lines. 149*7c478bd9Sstevel@tonic-gate */ 150*7c478bd9Sstevel@tonic-gate typedef struct { 151*7c478bd9Sstevel@tonic-gate FreeList *node_mem; /* A freelist of GlhLineNode objects */ 152*7c478bd9Sstevel@tonic-gate GlhLineNode *head; /* The oldest line in the list */ 153*7c478bd9Sstevel@tonic-gate GlhLineNode *tail; /* The newest line in the list */ 154*7c478bd9Sstevel@tonic-gate } GlhLineList; 155*7c478bd9Sstevel@tonic-gate 156*7c478bd9Sstevel@tonic-gate /* 157*7c478bd9Sstevel@tonic-gate * The _glh_lookup_history() returns copies of history lines in a 158*7c478bd9Sstevel@tonic-gate * dynamically allocated array. This array is initially allocated 159*7c478bd9Sstevel@tonic-gate * GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be 160*7c478bd9Sstevel@tonic-gate * too small, realloc() is used to increase its size to the required 161*7c478bd9Sstevel@tonic-gate * size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to 162*7c478bd9Sstevel@tonic-gate * reduce the number of realloc() operations needed. 163*7c478bd9Sstevel@tonic-gate */ 164*7c478bd9Sstevel@tonic-gate #define GLH_LBUF_SIZE 300 165*7c478bd9Sstevel@tonic-gate #define GLH_LBUF_MARGIN 100 166*7c478bd9Sstevel@tonic-gate 167*7c478bd9Sstevel@tonic-gate /* 168*7c478bd9Sstevel@tonic-gate * Encapsulate all of the resources needed to store historical input lines. 169*7c478bd9Sstevel@tonic-gate */ 170*7c478bd9Sstevel@tonic-gate struct GlHistory { 171*7c478bd9Sstevel@tonic-gate ErrMsg *err; /* The error-reporting buffer */ 172*7c478bd9Sstevel@tonic-gate GlhLineSeg *buffer; /* An array of sub-line nodes to be partitioned */ 173*7c478bd9Sstevel@tonic-gate /* into lists of sub-strings recording input lines. */ 174*7c478bd9Sstevel@tonic-gate int nbuff; /* The allocated dimension of buffer[] */ 175*7c478bd9Sstevel@tonic-gate GlhLineSeg *unused; /* The list of free nodes in buffer[] */ 176*7c478bd9Sstevel@tonic-gate GlhLineList list; /* A time ordered list of history lines */ 177*7c478bd9Sstevel@tonic-gate GlhLineNode *recall; /* The last line recalled, or NULL if no recall */ 178*7c478bd9Sstevel@tonic-gate /* session is currently active. */ 179*7c478bd9Sstevel@tonic-gate GlhLineNode *id_node;/* The node at which the last ID search terminated */ 180*7c478bd9Sstevel@tonic-gate GlhLineHash hash; /* A hash-table of reference-counted history lines */ 181*7c478bd9Sstevel@tonic-gate GlhHashNode *prefix; /* A pointer to a line containing the prefix that */ 182*7c478bd9Sstevel@tonic-gate /* is being searched for. Note that if prefix==NULL */ 183*7c478bd9Sstevel@tonic-gate /* and prefix_len>0, this means that no line in */ 184*7c478bd9Sstevel@tonic-gate /* the buffer starts with the requested prefix. */ 185*7c478bd9Sstevel@tonic-gate int prefix_len; /* The length of the prefix being searched for. */ 186*7c478bd9Sstevel@tonic-gate char *lbuf; /* The array in which _glh_lookup_history() returns */ 187*7c478bd9Sstevel@tonic-gate /* history lines */ 188*7c478bd9Sstevel@tonic-gate int lbuf_dim; /* The allocated size of lbuf[] */ 189*7c478bd9Sstevel@tonic-gate int nbusy; /* The number of line segments in buffer[] that are */ 190*7c478bd9Sstevel@tonic-gate /* currently being used to record sub-lines */ 191*7c478bd9Sstevel@tonic-gate int nfree; /* The number of line segments in buffer that are */ 192*7c478bd9Sstevel@tonic-gate /* not currently being used to record sub-lines */ 193*7c478bd9Sstevel@tonic-gate unsigned long seq; /* The next ID to assign to a line node */ 194*7c478bd9Sstevel@tonic-gate unsigned group; /* The identifier of the current history group */ 195*7c478bd9Sstevel@tonic-gate int nline; /* The number of lines currently in the history list */ 196*7c478bd9Sstevel@tonic-gate int max_lines; /* Either -1 or a ceiling on the number of lines */ 197*7c478bd9Sstevel@tonic-gate int enable; /* If false, ignore history additions and lookups */ 198*7c478bd9Sstevel@tonic-gate }; 199*7c478bd9Sstevel@tonic-gate 200*7c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM 201*7c478bd9Sstevel@tonic-gate static int _glh_cant_load_history(GlHistory *glh, const char *filename, 202*7c478bd9Sstevel@tonic-gate int lineno, const char *message, FILE *fp); 203*7c478bd9Sstevel@tonic-gate static int _glh_cant_save_history(GlHistory *glh, const char *message, 204*7c478bd9Sstevel@tonic-gate const char *filename, FILE *fp); 205*7c478bd9Sstevel@tonic-gate static int _glh_write_timestamp(FILE *fp, time_t timestamp); 206*7c478bd9Sstevel@tonic-gate static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp); 207*7c478bd9Sstevel@tonic-gate #endif 208*7c478bd9Sstevel@tonic-gate static void _glh_discard_line(GlHistory *glh, GlhLineNode *node); 209*7c478bd9Sstevel@tonic-gate static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id); 210*7c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, 211*7c478bd9Sstevel@tonic-gate size_t n); 212*7c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode); 213*7c478bd9Sstevel@tonic-gate static int _glh_prepare_for_recall(GlHistory *glh, char *line); 214*7c478bd9Sstevel@tonic-gate 215*7c478bd9Sstevel@tonic-gate /* 216*7c478bd9Sstevel@tonic-gate * The following structure and functions are used to iterate through 217*7c478bd9Sstevel@tonic-gate * the characters of a segmented history line. 218*7c478bd9Sstevel@tonic-gate */ 219*7c478bd9Sstevel@tonic-gate typedef struct { 220*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* The line segment that the next character will */ 221*7c478bd9Sstevel@tonic-gate /* be returned from. */ 222*7c478bd9Sstevel@tonic-gate int posn; /* The index in the above line segment, containing */ 223*7c478bd9Sstevel@tonic-gate /* the next unread character. */ 224*7c478bd9Sstevel@tonic-gate char c; /* The current character in the input line */ 225*7c478bd9Sstevel@tonic-gate } GlhLineStream; 226*7c478bd9Sstevel@tonic-gate static void glh_init_stream(GlhLineStream *str, GlhHashNode *line); 227*7c478bd9Sstevel@tonic-gate static void glh_step_stream(GlhLineStream *str); 228*7c478bd9Sstevel@tonic-gate 229*7c478bd9Sstevel@tonic-gate /* 230*7c478bd9Sstevel@tonic-gate * See if search prefix contains any globbing characters. 231*7c478bd9Sstevel@tonic-gate */ 232*7c478bd9Sstevel@tonic-gate static int glh_contains_glob(GlhHashNode *prefix); 233*7c478bd9Sstevel@tonic-gate /* 234*7c478bd9Sstevel@tonic-gate * Match a line against a search pattern. 235*7c478bd9Sstevel@tonic-gate */ 236*7c478bd9Sstevel@tonic-gate static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr); 237*7c478bd9Sstevel@tonic-gate static int glh_matches_range(char c, GlhLineStream *pstr); 238*7c478bd9Sstevel@tonic-gate 239*7c478bd9Sstevel@tonic-gate /*....................................................................... 240*7c478bd9Sstevel@tonic-gate * Create a line history maintenance object. 241*7c478bd9Sstevel@tonic-gate * 242*7c478bd9Sstevel@tonic-gate * Input: 243*7c478bd9Sstevel@tonic-gate * buflen size_t The number of bytes to allocate to the 244*7c478bd9Sstevel@tonic-gate * buffer that is used to record all of the 245*7c478bd9Sstevel@tonic-gate * most recent lines of user input that will fit. 246*7c478bd9Sstevel@tonic-gate * If buflen==0, no buffer will be allocated. 247*7c478bd9Sstevel@tonic-gate * Output: 248*7c478bd9Sstevel@tonic-gate * return GlHistory * The new object, or NULL on error. 249*7c478bd9Sstevel@tonic-gate */ 250*7c478bd9Sstevel@tonic-gate GlHistory *_new_GlHistory(size_t buflen) 251*7c478bd9Sstevel@tonic-gate { 252*7c478bd9Sstevel@tonic-gate GlHistory *glh; /* The object to be returned */ 253*7c478bd9Sstevel@tonic-gate int i; 254*7c478bd9Sstevel@tonic-gate /* 255*7c478bd9Sstevel@tonic-gate * Allocate the container. 256*7c478bd9Sstevel@tonic-gate */ 257*7c478bd9Sstevel@tonic-gate glh = (GlHistory *) malloc(sizeof(GlHistory)); 258*7c478bd9Sstevel@tonic-gate if(!glh) { 259*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 260*7c478bd9Sstevel@tonic-gate return NULL; 261*7c478bd9Sstevel@tonic-gate }; 262*7c478bd9Sstevel@tonic-gate /* 263*7c478bd9Sstevel@tonic-gate * Before attempting any operation that might fail, initialize the 264*7c478bd9Sstevel@tonic-gate * container at least up to the point at which it can safely be passed 265*7c478bd9Sstevel@tonic-gate * to _del_GlHistory(). 266*7c478bd9Sstevel@tonic-gate */ 267*7c478bd9Sstevel@tonic-gate glh->err = NULL; 268*7c478bd9Sstevel@tonic-gate glh->buffer = NULL; 269*7c478bd9Sstevel@tonic-gate glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 270*7c478bd9Sstevel@tonic-gate glh->unused = NULL; 271*7c478bd9Sstevel@tonic-gate glh->list.node_mem = NULL; 272*7c478bd9Sstevel@tonic-gate glh->list.head = glh->list.tail = NULL; 273*7c478bd9Sstevel@tonic-gate glh->recall = NULL; 274*7c478bd9Sstevel@tonic-gate glh->id_node = NULL; 275*7c478bd9Sstevel@tonic-gate glh->hash.node_mem = NULL; 276*7c478bd9Sstevel@tonic-gate for(i=0; i<GLH_HASH_SIZE; i++) 277*7c478bd9Sstevel@tonic-gate glh->hash.bucket[i].lines = NULL; 278*7c478bd9Sstevel@tonic-gate glh->prefix = NULL; 279*7c478bd9Sstevel@tonic-gate glh->lbuf = NULL; 280*7c478bd9Sstevel@tonic-gate glh->lbuf_dim = 0; 281*7c478bd9Sstevel@tonic-gate glh->nbusy = 0; 282*7c478bd9Sstevel@tonic-gate glh->nfree = glh->nbuff; 283*7c478bd9Sstevel@tonic-gate glh->seq = 0; 284*7c478bd9Sstevel@tonic-gate glh->group = 0; 285*7c478bd9Sstevel@tonic-gate glh->nline = 0; 286*7c478bd9Sstevel@tonic-gate glh->max_lines = -1; 287*7c478bd9Sstevel@tonic-gate glh->enable = 1; 288*7c478bd9Sstevel@tonic-gate /* 289*7c478bd9Sstevel@tonic-gate * Allocate a place to record error messages. 290*7c478bd9Sstevel@tonic-gate */ 291*7c478bd9Sstevel@tonic-gate glh->err = _new_ErrMsg(); 292*7c478bd9Sstevel@tonic-gate if(!glh->err) 293*7c478bd9Sstevel@tonic-gate return _del_GlHistory(glh); 294*7c478bd9Sstevel@tonic-gate /* 295*7c478bd9Sstevel@tonic-gate * Allocate the buffer, if required. 296*7c478bd9Sstevel@tonic-gate */ 297*7c478bd9Sstevel@tonic-gate if(glh->nbuff > 0) { 298*7c478bd9Sstevel@tonic-gate glh->nbuff = glh->nfree; 299*7c478bd9Sstevel@tonic-gate glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff); 300*7c478bd9Sstevel@tonic-gate if(!glh->buffer) { 301*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 302*7c478bd9Sstevel@tonic-gate return _del_GlHistory(glh); 303*7c478bd9Sstevel@tonic-gate }; 304*7c478bd9Sstevel@tonic-gate /* 305*7c478bd9Sstevel@tonic-gate * All nodes of the buffer are currently unused, so link them all into 306*7c478bd9Sstevel@tonic-gate * a list and make glh->unused point to the head of this list. 307*7c478bd9Sstevel@tonic-gate */ 308*7c478bd9Sstevel@tonic-gate glh->unused = glh->buffer; 309*7c478bd9Sstevel@tonic-gate for(i=0; i<glh->nbuff-1; i++) { 310*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg = glh->unused + i; 311*7c478bd9Sstevel@tonic-gate seg->next = seg + 1; 312*7c478bd9Sstevel@tonic-gate }; 313*7c478bd9Sstevel@tonic-gate glh->unused[i].next = NULL; 314*7c478bd9Sstevel@tonic-gate }; 315*7c478bd9Sstevel@tonic-gate /* 316*7c478bd9Sstevel@tonic-gate * Allocate the GlhLineNode freelist. 317*7c478bd9Sstevel@tonic-gate */ 318*7c478bd9Sstevel@tonic-gate glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR); 319*7c478bd9Sstevel@tonic-gate if(!glh->list.node_mem) 320*7c478bd9Sstevel@tonic-gate return _del_GlHistory(glh); 321*7c478bd9Sstevel@tonic-gate /* 322*7c478bd9Sstevel@tonic-gate * Allocate the GlhHashNode freelist. 323*7c478bd9Sstevel@tonic-gate */ 324*7c478bd9Sstevel@tonic-gate glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR); 325*7c478bd9Sstevel@tonic-gate if(!glh->hash.node_mem) 326*7c478bd9Sstevel@tonic-gate return _del_GlHistory(glh); 327*7c478bd9Sstevel@tonic-gate /* 328*7c478bd9Sstevel@tonic-gate * Allocate the array that _glh_lookup_history() uses to return a 329*7c478bd9Sstevel@tonic-gate * copy of a given history line. This will be resized when necessary. 330*7c478bd9Sstevel@tonic-gate */ 331*7c478bd9Sstevel@tonic-gate glh->lbuf_dim = GLH_LBUF_SIZE; 332*7c478bd9Sstevel@tonic-gate glh->lbuf = (char *) malloc(glh->lbuf_dim); 333*7c478bd9Sstevel@tonic-gate if(!glh->lbuf) { 334*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 335*7c478bd9Sstevel@tonic-gate return _del_GlHistory(glh); 336*7c478bd9Sstevel@tonic-gate }; 337*7c478bd9Sstevel@tonic-gate return glh; 338*7c478bd9Sstevel@tonic-gate } 339*7c478bd9Sstevel@tonic-gate 340*7c478bd9Sstevel@tonic-gate /*....................................................................... 341*7c478bd9Sstevel@tonic-gate * Delete a GlHistory object. 342*7c478bd9Sstevel@tonic-gate * 343*7c478bd9Sstevel@tonic-gate * Input: 344*7c478bd9Sstevel@tonic-gate * glh GlHistory * The object to be deleted. 345*7c478bd9Sstevel@tonic-gate * Output: 346*7c478bd9Sstevel@tonic-gate * return GlHistory * The deleted object (always NULL). 347*7c478bd9Sstevel@tonic-gate */ 348*7c478bd9Sstevel@tonic-gate GlHistory *_del_GlHistory(GlHistory *glh) 349*7c478bd9Sstevel@tonic-gate { 350*7c478bd9Sstevel@tonic-gate if(glh) { 351*7c478bd9Sstevel@tonic-gate /* 352*7c478bd9Sstevel@tonic-gate * Delete the error-message buffer. 353*7c478bd9Sstevel@tonic-gate */ 354*7c478bd9Sstevel@tonic-gate glh->err = _del_ErrMsg(glh->err); 355*7c478bd9Sstevel@tonic-gate /* 356*7c478bd9Sstevel@tonic-gate * Delete the buffer. 357*7c478bd9Sstevel@tonic-gate */ 358*7c478bd9Sstevel@tonic-gate if(glh->buffer) { 359*7c478bd9Sstevel@tonic-gate free(glh->buffer); 360*7c478bd9Sstevel@tonic-gate glh->buffer = NULL; 361*7c478bd9Sstevel@tonic-gate glh->unused = NULL; 362*7c478bd9Sstevel@tonic-gate }; 363*7c478bd9Sstevel@tonic-gate /* 364*7c478bd9Sstevel@tonic-gate * Delete the freelist of GlhLineNode's. 365*7c478bd9Sstevel@tonic-gate */ 366*7c478bd9Sstevel@tonic-gate glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1); 367*7c478bd9Sstevel@tonic-gate /* 368*7c478bd9Sstevel@tonic-gate * The contents of the list were deleted by deleting the freelist. 369*7c478bd9Sstevel@tonic-gate */ 370*7c478bd9Sstevel@tonic-gate glh->list.head = NULL; 371*7c478bd9Sstevel@tonic-gate glh->list.tail = NULL; 372*7c478bd9Sstevel@tonic-gate /* 373*7c478bd9Sstevel@tonic-gate * Delete the freelist of GlhHashNode's. 374*7c478bd9Sstevel@tonic-gate */ 375*7c478bd9Sstevel@tonic-gate glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1); 376*7c478bd9Sstevel@tonic-gate /* 377*7c478bd9Sstevel@tonic-gate * Delete the lookup buffer. 378*7c478bd9Sstevel@tonic-gate */ 379*7c478bd9Sstevel@tonic-gate if(glh->lbuf) 380*7c478bd9Sstevel@tonic-gate free(glh->lbuf); 381*7c478bd9Sstevel@tonic-gate /* 382*7c478bd9Sstevel@tonic-gate * Delete the container. 383*7c478bd9Sstevel@tonic-gate */ 384*7c478bd9Sstevel@tonic-gate free(glh); 385*7c478bd9Sstevel@tonic-gate }; 386*7c478bd9Sstevel@tonic-gate return NULL; 387*7c478bd9Sstevel@tonic-gate } 388*7c478bd9Sstevel@tonic-gate 389*7c478bd9Sstevel@tonic-gate /*....................................................................... 390*7c478bd9Sstevel@tonic-gate * Append a new line to the history list, deleting old lines to make 391*7c478bd9Sstevel@tonic-gate * room, if needed. 392*7c478bd9Sstevel@tonic-gate * 393*7c478bd9Sstevel@tonic-gate * Input: 394*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 395*7c478bd9Sstevel@tonic-gate * line char * The line to be archived. 396*7c478bd9Sstevel@tonic-gate * force int Unless this flag is non-zero, empty lines aren't 397*7c478bd9Sstevel@tonic-gate * archived. This flag requests that the line be 398*7c478bd9Sstevel@tonic-gate * archived regardless. 399*7c478bd9Sstevel@tonic-gate * Output: 400*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 401*7c478bd9Sstevel@tonic-gate * 1 - Error. 402*7c478bd9Sstevel@tonic-gate */ 403*7c478bd9Sstevel@tonic-gate int _glh_add_history(GlHistory *glh, const char *line, int force) 404*7c478bd9Sstevel@tonic-gate { 405*7c478bd9Sstevel@tonic-gate int slen; /* The length of the line to be recorded (minus the '\0') */ 406*7c478bd9Sstevel@tonic-gate int empty; /* True if the string is empty */ 407*7c478bd9Sstevel@tonic-gate const char *nlptr; /* A pointer to a newline character in line[] */ 408*7c478bd9Sstevel@tonic-gate GlhHashNode *hnode; /* The hash-table node of the line */ 409*7c478bd9Sstevel@tonic-gate GlhLineNode *lnode; /* A node in the time-ordered list of lines */ 410*7c478bd9Sstevel@tonic-gate int i; 411*7c478bd9Sstevel@tonic-gate /* 412*7c478bd9Sstevel@tonic-gate * Check the arguments. 413*7c478bd9Sstevel@tonic-gate */ 414*7c478bd9Sstevel@tonic-gate if(!glh || !line) { 415*7c478bd9Sstevel@tonic-gate errno = EINVAL; 416*7c478bd9Sstevel@tonic-gate return 1; 417*7c478bd9Sstevel@tonic-gate }; 418*7c478bd9Sstevel@tonic-gate /* 419*7c478bd9Sstevel@tonic-gate * Is history enabled? 420*7c478bd9Sstevel@tonic-gate */ 421*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 422*7c478bd9Sstevel@tonic-gate return 0; 423*7c478bd9Sstevel@tonic-gate /* 424*7c478bd9Sstevel@tonic-gate * Cancel any ongoing search. 425*7c478bd9Sstevel@tonic-gate */ 426*7c478bd9Sstevel@tonic-gate if(_glh_cancel_search(glh)) 427*7c478bd9Sstevel@tonic-gate return 1; 428*7c478bd9Sstevel@tonic-gate /* 429*7c478bd9Sstevel@tonic-gate * How long is the string to be recorded, being careful not to include 430*7c478bd9Sstevel@tonic-gate * any terminating '\n' character. 431*7c478bd9Sstevel@tonic-gate */ 432*7c478bd9Sstevel@tonic-gate nlptr = strchr(line, '\n'); 433*7c478bd9Sstevel@tonic-gate if(nlptr) 434*7c478bd9Sstevel@tonic-gate slen = (nlptr - line); 435*7c478bd9Sstevel@tonic-gate else 436*7c478bd9Sstevel@tonic-gate slen = strlen(line); 437*7c478bd9Sstevel@tonic-gate /* 438*7c478bd9Sstevel@tonic-gate * Is the line empty? 439*7c478bd9Sstevel@tonic-gate */ 440*7c478bd9Sstevel@tonic-gate empty = 1; 441*7c478bd9Sstevel@tonic-gate for(i=0; i<slen && empty; i++) 442*7c478bd9Sstevel@tonic-gate empty = isspace((int)(unsigned char) line[i]); 443*7c478bd9Sstevel@tonic-gate /* 444*7c478bd9Sstevel@tonic-gate * If the line is empty, don't add it to the buffer unless explicitly 445*7c478bd9Sstevel@tonic-gate * told to. 446*7c478bd9Sstevel@tonic-gate */ 447*7c478bd9Sstevel@tonic-gate if(empty && !force) 448*7c478bd9Sstevel@tonic-gate return 0; 449*7c478bd9Sstevel@tonic-gate /* 450*7c478bd9Sstevel@tonic-gate * Has an upper limit to the number of lines in the history list been 451*7c478bd9Sstevel@tonic-gate * specified? 452*7c478bd9Sstevel@tonic-gate */ 453*7c478bd9Sstevel@tonic-gate if(glh->max_lines >= 0) { 454*7c478bd9Sstevel@tonic-gate /* 455*7c478bd9Sstevel@tonic-gate * If necessary, remove old lines until there is room to add one new 456*7c478bd9Sstevel@tonic-gate * line without exceeding the specified line limit. 457*7c478bd9Sstevel@tonic-gate */ 458*7c478bd9Sstevel@tonic-gate while(glh->nline > 0 && glh->nline >= glh->max_lines) 459*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, glh->list.head); 460*7c478bd9Sstevel@tonic-gate /* 461*7c478bd9Sstevel@tonic-gate * We can't archive the line if the maximum number of lines allowed is 462*7c478bd9Sstevel@tonic-gate * zero. 463*7c478bd9Sstevel@tonic-gate */ 464*7c478bd9Sstevel@tonic-gate if(glh->max_lines == 0) 465*7c478bd9Sstevel@tonic-gate return 0; 466*7c478bd9Sstevel@tonic-gate }; 467*7c478bd9Sstevel@tonic-gate /* 468*7c478bd9Sstevel@tonic-gate * Unless already stored, store a copy of the line in the history buffer, 469*7c478bd9Sstevel@tonic-gate * then return a reference-counted hash-node pointer to this copy. 470*7c478bd9Sstevel@tonic-gate */ 471*7c478bd9Sstevel@tonic-gate hnode = _glh_acquire_copy(glh, line, slen); 472*7c478bd9Sstevel@tonic-gate if(!hnode) { 473*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); 474*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 475*7c478bd9Sstevel@tonic-gate return 1; 476*7c478bd9Sstevel@tonic-gate }; 477*7c478bd9Sstevel@tonic-gate /* 478*7c478bd9Sstevel@tonic-gate * Allocate a new node in the time-ordered list of lines. 479*7c478bd9Sstevel@tonic-gate */ 480*7c478bd9Sstevel@tonic-gate lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem); 481*7c478bd9Sstevel@tonic-gate /* 482*7c478bd9Sstevel@tonic-gate * If a new line-node couldn't be allocated, discard our copy of the 483*7c478bd9Sstevel@tonic-gate * stored line before reporting the error. 484*7c478bd9Sstevel@tonic-gate */ 485*7c478bd9Sstevel@tonic-gate if(!lnode) { 486*7c478bd9Sstevel@tonic-gate hnode = _glh_discard_copy(glh, hnode); 487*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG); 488*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 489*7c478bd9Sstevel@tonic-gate return 1; 490*7c478bd9Sstevel@tonic-gate }; 491*7c478bd9Sstevel@tonic-gate /* 492*7c478bd9Sstevel@tonic-gate * Record a pointer to the hash-table record of the line in the new 493*7c478bd9Sstevel@tonic-gate * list node. 494*7c478bd9Sstevel@tonic-gate */ 495*7c478bd9Sstevel@tonic-gate lnode->id = glh->seq++; 496*7c478bd9Sstevel@tonic-gate lnode->timestamp = time(NULL); 497*7c478bd9Sstevel@tonic-gate lnode->group = glh->group; 498*7c478bd9Sstevel@tonic-gate lnode->line = hnode; 499*7c478bd9Sstevel@tonic-gate /* 500*7c478bd9Sstevel@tonic-gate * Append the new node to the end of the time-ordered list. 501*7c478bd9Sstevel@tonic-gate */ 502*7c478bd9Sstevel@tonic-gate if(glh->list.head) 503*7c478bd9Sstevel@tonic-gate glh->list.tail->next = lnode; 504*7c478bd9Sstevel@tonic-gate else 505*7c478bd9Sstevel@tonic-gate glh->list.head = lnode; 506*7c478bd9Sstevel@tonic-gate lnode->next = NULL; 507*7c478bd9Sstevel@tonic-gate lnode->prev = glh->list.tail; 508*7c478bd9Sstevel@tonic-gate glh->list.tail = lnode; 509*7c478bd9Sstevel@tonic-gate /* 510*7c478bd9Sstevel@tonic-gate * Record the addition of a line to the list. 511*7c478bd9Sstevel@tonic-gate */ 512*7c478bd9Sstevel@tonic-gate glh->nline++; 513*7c478bd9Sstevel@tonic-gate return 0; 514*7c478bd9Sstevel@tonic-gate } 515*7c478bd9Sstevel@tonic-gate 516*7c478bd9Sstevel@tonic-gate /*....................................................................... 517*7c478bd9Sstevel@tonic-gate * Recall the next oldest line that has the search prefix last recorded 518*7c478bd9Sstevel@tonic-gate * by _glh_search_prefix(). 519*7c478bd9Sstevel@tonic-gate * 520*7c478bd9Sstevel@tonic-gate * Input: 521*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 522*7c478bd9Sstevel@tonic-gate * line char * The input line buffer. On input this should contain 523*7c478bd9Sstevel@tonic-gate * the current input line, and on output, if anything 524*7c478bd9Sstevel@tonic-gate * was found, its contents will have been replaced 525*7c478bd9Sstevel@tonic-gate * with the matching line. 526*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimension of the line buffer. 527*7c478bd9Sstevel@tonic-gate * Output: 528*7c478bd9Sstevel@tonic-gate * return char * A pointer to line[0], or NULL if not found. 529*7c478bd9Sstevel@tonic-gate */ 530*7c478bd9Sstevel@tonic-gate char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim) 531*7c478bd9Sstevel@tonic-gate { 532*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line location node being checked */ 533*7c478bd9Sstevel@tonic-gate GlhHashNode *old_line; /* The previous recalled line */ 534*7c478bd9Sstevel@tonic-gate /* 535*7c478bd9Sstevel@tonic-gate * Check the arguments. 536*7c478bd9Sstevel@tonic-gate */ 537*7c478bd9Sstevel@tonic-gate if(!glh || !line) { 538*7c478bd9Sstevel@tonic-gate if(glh) 539*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 540*7c478bd9Sstevel@tonic-gate errno = EINVAL; 541*7c478bd9Sstevel@tonic-gate return NULL; 542*7c478bd9Sstevel@tonic-gate }; 543*7c478bd9Sstevel@tonic-gate /* 544*7c478bd9Sstevel@tonic-gate * Is history enabled? 545*7c478bd9Sstevel@tonic-gate */ 546*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 547*7c478bd9Sstevel@tonic-gate return NULL; 548*7c478bd9Sstevel@tonic-gate /* 549*7c478bd9Sstevel@tonic-gate * Check the line dimensions. 550*7c478bd9Sstevel@tonic-gate */ 551*7c478bd9Sstevel@tonic-gate if(dim < strlen(line) + 1) { 552*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 553*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 554*7c478bd9Sstevel@tonic-gate errno = EINVAL; 555*7c478bd9Sstevel@tonic-gate return NULL; 556*7c478bd9Sstevel@tonic-gate }; 557*7c478bd9Sstevel@tonic-gate /* 558*7c478bd9Sstevel@tonic-gate * Preserve the input line if needed. 559*7c478bd9Sstevel@tonic-gate */ 560*7c478bd9Sstevel@tonic-gate if(_glh_prepare_for_recall(glh, line)) 561*7c478bd9Sstevel@tonic-gate return NULL; 562*7c478bd9Sstevel@tonic-gate /* 563*7c478bd9Sstevel@tonic-gate * From where should we start the search? 564*7c478bd9Sstevel@tonic-gate */ 565*7c478bd9Sstevel@tonic-gate if(glh->recall) { 566*7c478bd9Sstevel@tonic-gate node = glh->recall->prev; 567*7c478bd9Sstevel@tonic-gate old_line = glh->recall->line; 568*7c478bd9Sstevel@tonic-gate } else { 569*7c478bd9Sstevel@tonic-gate node = glh->list.tail; 570*7c478bd9Sstevel@tonic-gate old_line = NULL; 571*7c478bd9Sstevel@tonic-gate }; 572*7c478bd9Sstevel@tonic-gate /* 573*7c478bd9Sstevel@tonic-gate * Search backwards through the list for the first match with the 574*7c478bd9Sstevel@tonic-gate * prefix string that differs from the last line that was recalled. 575*7c478bd9Sstevel@tonic-gate */ 576*7c478bd9Sstevel@tonic-gate while(node && (node->group != glh->group || node->line == old_line || 577*7c478bd9Sstevel@tonic-gate !_glh_line_matches_prefix(node->line, glh->prefix))) 578*7c478bd9Sstevel@tonic-gate node = node->prev; 579*7c478bd9Sstevel@tonic-gate /* 580*7c478bd9Sstevel@tonic-gate * Was a matching line found? 581*7c478bd9Sstevel@tonic-gate */ 582*7c478bd9Sstevel@tonic-gate if(node) { 583*7c478bd9Sstevel@tonic-gate /* 584*7c478bd9Sstevel@tonic-gate * Recall the found node as the starting point for subsequent 585*7c478bd9Sstevel@tonic-gate * searches. 586*7c478bd9Sstevel@tonic-gate */ 587*7c478bd9Sstevel@tonic-gate glh->recall = node; 588*7c478bd9Sstevel@tonic-gate /* 589*7c478bd9Sstevel@tonic-gate * Copy the matching line into the provided line buffer. 590*7c478bd9Sstevel@tonic-gate */ 591*7c478bd9Sstevel@tonic-gate _glh_return_line(node->line, line, dim); 592*7c478bd9Sstevel@tonic-gate /* 593*7c478bd9Sstevel@tonic-gate * Return it. 594*7c478bd9Sstevel@tonic-gate */ 595*7c478bd9Sstevel@tonic-gate return line; 596*7c478bd9Sstevel@tonic-gate }; 597*7c478bd9Sstevel@tonic-gate /* 598*7c478bd9Sstevel@tonic-gate * No match was found. 599*7c478bd9Sstevel@tonic-gate */ 600*7c478bd9Sstevel@tonic-gate return NULL; 601*7c478bd9Sstevel@tonic-gate } 602*7c478bd9Sstevel@tonic-gate 603*7c478bd9Sstevel@tonic-gate /*....................................................................... 604*7c478bd9Sstevel@tonic-gate * Recall the next newest line that has the search prefix last recorded 605*7c478bd9Sstevel@tonic-gate * by _glh_search_prefix(). 606*7c478bd9Sstevel@tonic-gate * 607*7c478bd9Sstevel@tonic-gate * Input: 608*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 609*7c478bd9Sstevel@tonic-gate * line char * The input line buffer. On input this should contain 610*7c478bd9Sstevel@tonic-gate * the current input line, and on output, if anything 611*7c478bd9Sstevel@tonic-gate * was found, its contents will have been replaced 612*7c478bd9Sstevel@tonic-gate * with the matching line. 613*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimensions of the line buffer. 614*7c478bd9Sstevel@tonic-gate * Output: 615*7c478bd9Sstevel@tonic-gate * return char * The line requested, or NULL if no matching line 616*7c478bd9Sstevel@tonic-gate * was found. 617*7c478bd9Sstevel@tonic-gate */ 618*7c478bd9Sstevel@tonic-gate char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim) 619*7c478bd9Sstevel@tonic-gate { 620*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line location node being checked */ 621*7c478bd9Sstevel@tonic-gate GlhHashNode *old_line; /* The previous recalled line */ 622*7c478bd9Sstevel@tonic-gate /* 623*7c478bd9Sstevel@tonic-gate * Check the arguments. 624*7c478bd9Sstevel@tonic-gate */ 625*7c478bd9Sstevel@tonic-gate if(!glh || !line) { 626*7c478bd9Sstevel@tonic-gate if(glh) 627*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 628*7c478bd9Sstevel@tonic-gate errno = EINVAL; 629*7c478bd9Sstevel@tonic-gate return NULL; 630*7c478bd9Sstevel@tonic-gate }; 631*7c478bd9Sstevel@tonic-gate /* 632*7c478bd9Sstevel@tonic-gate * Is history enabled? 633*7c478bd9Sstevel@tonic-gate */ 634*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 635*7c478bd9Sstevel@tonic-gate return NULL; 636*7c478bd9Sstevel@tonic-gate /* 637*7c478bd9Sstevel@tonic-gate * Check the line dimensions. 638*7c478bd9Sstevel@tonic-gate */ 639*7c478bd9Sstevel@tonic-gate if(dim < strlen(line) + 1) { 640*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 641*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 642*7c478bd9Sstevel@tonic-gate errno = EINVAL; 643*7c478bd9Sstevel@tonic-gate return NULL; 644*7c478bd9Sstevel@tonic-gate }; 645*7c478bd9Sstevel@tonic-gate /* 646*7c478bd9Sstevel@tonic-gate * From where should we start the search? 647*7c478bd9Sstevel@tonic-gate */ 648*7c478bd9Sstevel@tonic-gate if(glh->recall) { 649*7c478bd9Sstevel@tonic-gate node = glh->recall->next; 650*7c478bd9Sstevel@tonic-gate old_line = glh->recall->line; 651*7c478bd9Sstevel@tonic-gate } else { 652*7c478bd9Sstevel@tonic-gate return NULL; 653*7c478bd9Sstevel@tonic-gate }; 654*7c478bd9Sstevel@tonic-gate /* 655*7c478bd9Sstevel@tonic-gate * Search forwards through the list for the first match with the 656*7c478bd9Sstevel@tonic-gate * prefix string. 657*7c478bd9Sstevel@tonic-gate */ 658*7c478bd9Sstevel@tonic-gate while(node && (node->group != glh->group || node->line == old_line || 659*7c478bd9Sstevel@tonic-gate !_glh_line_matches_prefix(node->line, glh->prefix))) 660*7c478bd9Sstevel@tonic-gate node = node->next; 661*7c478bd9Sstevel@tonic-gate /* 662*7c478bd9Sstevel@tonic-gate * Was a matching line found? 663*7c478bd9Sstevel@tonic-gate */ 664*7c478bd9Sstevel@tonic-gate if(node) { 665*7c478bd9Sstevel@tonic-gate /* 666*7c478bd9Sstevel@tonic-gate * Copy the matching line into the provided line buffer. 667*7c478bd9Sstevel@tonic-gate */ 668*7c478bd9Sstevel@tonic-gate _glh_return_line(node->line, line, dim); 669*7c478bd9Sstevel@tonic-gate /* 670*7c478bd9Sstevel@tonic-gate * Record the starting point of the next search. 671*7c478bd9Sstevel@tonic-gate */ 672*7c478bd9Sstevel@tonic-gate glh->recall = node; 673*7c478bd9Sstevel@tonic-gate /* 674*7c478bd9Sstevel@tonic-gate * If we just returned the line that was being entered when the search 675*7c478bd9Sstevel@tonic-gate * session first started, cancel the search. 676*7c478bd9Sstevel@tonic-gate */ 677*7c478bd9Sstevel@tonic-gate if(node == glh->list.tail) 678*7c478bd9Sstevel@tonic-gate _glh_cancel_search(glh); 679*7c478bd9Sstevel@tonic-gate /* 680*7c478bd9Sstevel@tonic-gate * Return the matching line to the user. 681*7c478bd9Sstevel@tonic-gate */ 682*7c478bd9Sstevel@tonic-gate return line; 683*7c478bd9Sstevel@tonic-gate }; 684*7c478bd9Sstevel@tonic-gate /* 685*7c478bd9Sstevel@tonic-gate * No match was found. 686*7c478bd9Sstevel@tonic-gate */ 687*7c478bd9Sstevel@tonic-gate return NULL; 688*7c478bd9Sstevel@tonic-gate } 689*7c478bd9Sstevel@tonic-gate 690*7c478bd9Sstevel@tonic-gate /*....................................................................... 691*7c478bd9Sstevel@tonic-gate * If a search is in progress, cancel it. 692*7c478bd9Sstevel@tonic-gate * 693*7c478bd9Sstevel@tonic-gate * This involves discarding the line that was temporarily saved by 694*7c478bd9Sstevel@tonic-gate * _glh_find_backwards() when the search was originally started, 695*7c478bd9Sstevel@tonic-gate * and reseting the search iteration pointer to NULL. 696*7c478bd9Sstevel@tonic-gate * 697*7c478bd9Sstevel@tonic-gate * Input: 698*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 699*7c478bd9Sstevel@tonic-gate * Output: 700*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 701*7c478bd9Sstevel@tonic-gate * 1 - Error. 702*7c478bd9Sstevel@tonic-gate */ 703*7c478bd9Sstevel@tonic-gate int _glh_cancel_search(GlHistory *glh) 704*7c478bd9Sstevel@tonic-gate { 705*7c478bd9Sstevel@tonic-gate /* 706*7c478bd9Sstevel@tonic-gate * Check the arguments. 707*7c478bd9Sstevel@tonic-gate */ 708*7c478bd9Sstevel@tonic-gate if(!glh) { 709*7c478bd9Sstevel@tonic-gate errno = EINVAL; 710*7c478bd9Sstevel@tonic-gate return 1; 711*7c478bd9Sstevel@tonic-gate }; 712*7c478bd9Sstevel@tonic-gate /* 713*7c478bd9Sstevel@tonic-gate * If there wasn't a search in progress, do nothing. 714*7c478bd9Sstevel@tonic-gate */ 715*7c478bd9Sstevel@tonic-gate if(!glh->recall) 716*7c478bd9Sstevel@tonic-gate return 0; 717*7c478bd9Sstevel@tonic-gate /* 718*7c478bd9Sstevel@tonic-gate * Reset the search pointers. Note that it is essential to set 719*7c478bd9Sstevel@tonic-gate * glh->recall to NULL before calling _glh_discard_line(), to avoid an 720*7c478bd9Sstevel@tonic-gate * infinite recursion. 721*7c478bd9Sstevel@tonic-gate */ 722*7c478bd9Sstevel@tonic-gate glh->recall = NULL; 723*7c478bd9Sstevel@tonic-gate /* 724*7c478bd9Sstevel@tonic-gate * Delete the node of the preserved line. 725*7c478bd9Sstevel@tonic-gate */ 726*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, glh->list.tail); 727*7c478bd9Sstevel@tonic-gate return 0; 728*7c478bd9Sstevel@tonic-gate } 729*7c478bd9Sstevel@tonic-gate 730*7c478bd9Sstevel@tonic-gate /*....................................................................... 731*7c478bd9Sstevel@tonic-gate * Set the prefix of subsequent history searches. 732*7c478bd9Sstevel@tonic-gate * 733*7c478bd9Sstevel@tonic-gate * Input: 734*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 735*7c478bd9Sstevel@tonic-gate * line const char * The command line who's prefix is to be used. 736*7c478bd9Sstevel@tonic-gate * prefix_len int The length of the prefix. 737*7c478bd9Sstevel@tonic-gate * Output: 738*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 739*7c478bd9Sstevel@tonic-gate * 1 - Error. 740*7c478bd9Sstevel@tonic-gate */ 741*7c478bd9Sstevel@tonic-gate int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len) 742*7c478bd9Sstevel@tonic-gate { 743*7c478bd9Sstevel@tonic-gate /* 744*7c478bd9Sstevel@tonic-gate * Check the arguments. 745*7c478bd9Sstevel@tonic-gate */ 746*7c478bd9Sstevel@tonic-gate if(!glh) { 747*7c478bd9Sstevel@tonic-gate errno = EINVAL; 748*7c478bd9Sstevel@tonic-gate return 1; 749*7c478bd9Sstevel@tonic-gate }; 750*7c478bd9Sstevel@tonic-gate /* 751*7c478bd9Sstevel@tonic-gate * Is history enabled? 752*7c478bd9Sstevel@tonic-gate */ 753*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 754*7c478bd9Sstevel@tonic-gate return 0; 755*7c478bd9Sstevel@tonic-gate /* 756*7c478bd9Sstevel@tonic-gate * Discard any existing prefix. 757*7c478bd9Sstevel@tonic-gate */ 758*7c478bd9Sstevel@tonic-gate glh->prefix = _glh_discard_copy(glh, glh->prefix); 759*7c478bd9Sstevel@tonic-gate /* 760*7c478bd9Sstevel@tonic-gate * Only store a copy of the prefix string if it isn't a zero-length string. 761*7c478bd9Sstevel@tonic-gate */ 762*7c478bd9Sstevel@tonic-gate if(prefix_len > 0) { 763*7c478bd9Sstevel@tonic-gate /* 764*7c478bd9Sstevel@tonic-gate * Get a reference-counted copy of the prefix from the history cache buffer. 765*7c478bd9Sstevel@tonic-gate */ 766*7c478bd9Sstevel@tonic-gate glh->prefix = _glh_acquire_copy(glh, line, prefix_len); 767*7c478bd9Sstevel@tonic-gate /* 768*7c478bd9Sstevel@tonic-gate * Was there insufficient buffer space? 769*7c478bd9Sstevel@tonic-gate */ 770*7c478bd9Sstevel@tonic-gate if(!glh->prefix) { 771*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "The search prefix is too long to store", 772*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 773*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 774*7c478bd9Sstevel@tonic-gate return 1; 775*7c478bd9Sstevel@tonic-gate }; 776*7c478bd9Sstevel@tonic-gate }; 777*7c478bd9Sstevel@tonic-gate return 0; 778*7c478bd9Sstevel@tonic-gate } 779*7c478bd9Sstevel@tonic-gate 780*7c478bd9Sstevel@tonic-gate /*....................................................................... 781*7c478bd9Sstevel@tonic-gate * Recall the oldest recorded line. 782*7c478bd9Sstevel@tonic-gate * 783*7c478bd9Sstevel@tonic-gate * Input: 784*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 785*7c478bd9Sstevel@tonic-gate * line char * The input line buffer. On input this should contain 786*7c478bd9Sstevel@tonic-gate * the current input line, and on output, its contents 787*7c478bd9Sstevel@tonic-gate * will have been replaced with the oldest line. 788*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimensions of the line buffer. 789*7c478bd9Sstevel@tonic-gate * Output: 790*7c478bd9Sstevel@tonic-gate * return char * A pointer to line[0], or NULL if not found. 791*7c478bd9Sstevel@tonic-gate */ 792*7c478bd9Sstevel@tonic-gate char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim) 793*7c478bd9Sstevel@tonic-gate { 794*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line location node being checked */ 795*7c478bd9Sstevel@tonic-gate /* 796*7c478bd9Sstevel@tonic-gate * Check the arguments. 797*7c478bd9Sstevel@tonic-gate */ 798*7c478bd9Sstevel@tonic-gate if(!glh || !line) { 799*7c478bd9Sstevel@tonic-gate if(glh) 800*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 801*7c478bd9Sstevel@tonic-gate errno = EINVAL; 802*7c478bd9Sstevel@tonic-gate return NULL; 803*7c478bd9Sstevel@tonic-gate }; 804*7c478bd9Sstevel@tonic-gate /* 805*7c478bd9Sstevel@tonic-gate * Is history enabled? 806*7c478bd9Sstevel@tonic-gate */ 807*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 808*7c478bd9Sstevel@tonic-gate return NULL; 809*7c478bd9Sstevel@tonic-gate /* 810*7c478bd9Sstevel@tonic-gate * Check the line dimensions. 811*7c478bd9Sstevel@tonic-gate */ 812*7c478bd9Sstevel@tonic-gate if(dim < strlen(line) + 1) { 813*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 814*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 815*7c478bd9Sstevel@tonic-gate errno = EINVAL; 816*7c478bd9Sstevel@tonic-gate return NULL; 817*7c478bd9Sstevel@tonic-gate }; 818*7c478bd9Sstevel@tonic-gate /* 819*7c478bd9Sstevel@tonic-gate * Preserve the input line if needed. 820*7c478bd9Sstevel@tonic-gate */ 821*7c478bd9Sstevel@tonic-gate if(_glh_prepare_for_recall(glh, line)) 822*7c478bd9Sstevel@tonic-gate return NULL; 823*7c478bd9Sstevel@tonic-gate /* 824*7c478bd9Sstevel@tonic-gate * Locate the oldest line that belongs to the current group. 825*7c478bd9Sstevel@tonic-gate */ 826*7c478bd9Sstevel@tonic-gate for(node=glh->list.head; node && node->group != glh->group; 827*7c478bd9Sstevel@tonic-gate node = node->next) 828*7c478bd9Sstevel@tonic-gate ; 829*7c478bd9Sstevel@tonic-gate /* 830*7c478bd9Sstevel@tonic-gate * No line found? 831*7c478bd9Sstevel@tonic-gate */ 832*7c478bd9Sstevel@tonic-gate if(!node) 833*7c478bd9Sstevel@tonic-gate return NULL; 834*7c478bd9Sstevel@tonic-gate /* 835*7c478bd9Sstevel@tonic-gate * Record the above node as the starting point for subsequent 836*7c478bd9Sstevel@tonic-gate * searches. 837*7c478bd9Sstevel@tonic-gate */ 838*7c478bd9Sstevel@tonic-gate glh->recall = node; 839*7c478bd9Sstevel@tonic-gate /* 840*7c478bd9Sstevel@tonic-gate * Copy the recalled line into the provided line buffer. 841*7c478bd9Sstevel@tonic-gate */ 842*7c478bd9Sstevel@tonic-gate _glh_return_line(node->line, line, dim); 843*7c478bd9Sstevel@tonic-gate /* 844*7c478bd9Sstevel@tonic-gate * If we just returned the line that was being entered when the search 845*7c478bd9Sstevel@tonic-gate * session first started, cancel the search. 846*7c478bd9Sstevel@tonic-gate */ 847*7c478bd9Sstevel@tonic-gate if(node == glh->list.tail) 848*7c478bd9Sstevel@tonic-gate _glh_cancel_search(glh); 849*7c478bd9Sstevel@tonic-gate return line; 850*7c478bd9Sstevel@tonic-gate } 851*7c478bd9Sstevel@tonic-gate 852*7c478bd9Sstevel@tonic-gate /*....................................................................... 853*7c478bd9Sstevel@tonic-gate * Recall the line that was being entered when the search started. 854*7c478bd9Sstevel@tonic-gate * 855*7c478bd9Sstevel@tonic-gate * Input: 856*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 857*7c478bd9Sstevel@tonic-gate * line char * The input line buffer. On input this should contain 858*7c478bd9Sstevel@tonic-gate * the current input line, and on output, its contents 859*7c478bd9Sstevel@tonic-gate * will have been replaced with the line that was 860*7c478bd9Sstevel@tonic-gate * being entered when the search was started. 861*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimensions of the line buffer. 862*7c478bd9Sstevel@tonic-gate * Output: 863*7c478bd9Sstevel@tonic-gate * return char * A pointer to line[0], or NULL if not found. 864*7c478bd9Sstevel@tonic-gate */ 865*7c478bd9Sstevel@tonic-gate char *_glh_current_line(GlHistory *glh, char *line, size_t dim) 866*7c478bd9Sstevel@tonic-gate { 867*7c478bd9Sstevel@tonic-gate /* 868*7c478bd9Sstevel@tonic-gate * Check the arguments. 869*7c478bd9Sstevel@tonic-gate */ 870*7c478bd9Sstevel@tonic-gate if(!glh || !line) { 871*7c478bd9Sstevel@tonic-gate if(glh) 872*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 873*7c478bd9Sstevel@tonic-gate errno = EINVAL; 874*7c478bd9Sstevel@tonic-gate return NULL; 875*7c478bd9Sstevel@tonic-gate }; 876*7c478bd9Sstevel@tonic-gate /* 877*7c478bd9Sstevel@tonic-gate * If history isn't enabled, or no history search has yet been started, 878*7c478bd9Sstevel@tonic-gate * ignore the call. 879*7c478bd9Sstevel@tonic-gate */ 880*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall) 881*7c478bd9Sstevel@tonic-gate return NULL; 882*7c478bd9Sstevel@tonic-gate /* 883*7c478bd9Sstevel@tonic-gate * Check the line dimensions. 884*7c478bd9Sstevel@tonic-gate */ 885*7c478bd9Sstevel@tonic-gate if(dim < strlen(line) + 1) { 886*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)", 887*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 888*7c478bd9Sstevel@tonic-gate errno = EINVAL; 889*7c478bd9Sstevel@tonic-gate return NULL; 890*7c478bd9Sstevel@tonic-gate }; 891*7c478bd9Sstevel@tonic-gate /* 892*7c478bd9Sstevel@tonic-gate * Copy the recalled line into the provided line buffer. 893*7c478bd9Sstevel@tonic-gate */ 894*7c478bd9Sstevel@tonic-gate _glh_return_line(glh->list.tail->line, line, dim); 895*7c478bd9Sstevel@tonic-gate /* 896*7c478bd9Sstevel@tonic-gate * Since we have returned to the starting point of the search, cancel it. 897*7c478bd9Sstevel@tonic-gate */ 898*7c478bd9Sstevel@tonic-gate _glh_cancel_search(glh); 899*7c478bd9Sstevel@tonic-gate return line; 900*7c478bd9Sstevel@tonic-gate } 901*7c478bd9Sstevel@tonic-gate 902*7c478bd9Sstevel@tonic-gate /*....................................................................... 903*7c478bd9Sstevel@tonic-gate * Query the id of a history line offset by a given number of lines from 904*7c478bd9Sstevel@tonic-gate * the one that is currently being recalled. If a recall session isn't 905*7c478bd9Sstevel@tonic-gate * in progress, or the offset points outside the history list, 0 is 906*7c478bd9Sstevel@tonic-gate * returned. 907*7c478bd9Sstevel@tonic-gate * 908*7c478bd9Sstevel@tonic-gate * Input: 909*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 910*7c478bd9Sstevel@tonic-gate * offset int The line offset (0 for the current line, < 0 911*7c478bd9Sstevel@tonic-gate * for an older line, > 0 for a newer line. 912*7c478bd9Sstevel@tonic-gate * Output: 913*7c478bd9Sstevel@tonic-gate * return GlhLineID The identifier of the line that is currently 914*7c478bd9Sstevel@tonic-gate * being recalled, or 0 if no recall session is 915*7c478bd9Sstevel@tonic-gate * currently in progress. 916*7c478bd9Sstevel@tonic-gate */ 917*7c478bd9Sstevel@tonic-gate GlhLineID _glh_line_id(GlHistory *glh, int offset) 918*7c478bd9Sstevel@tonic-gate { 919*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line location node being checked */ 920*7c478bd9Sstevel@tonic-gate /* 921*7c478bd9Sstevel@tonic-gate * Is history enabled? 922*7c478bd9Sstevel@tonic-gate */ 923*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 924*7c478bd9Sstevel@tonic-gate return 0; 925*7c478bd9Sstevel@tonic-gate /* 926*7c478bd9Sstevel@tonic-gate * Search forward 'offset' lines to find the required line. 927*7c478bd9Sstevel@tonic-gate */ 928*7c478bd9Sstevel@tonic-gate if(offset >= 0) { 929*7c478bd9Sstevel@tonic-gate for(node=glh->recall; node && offset != 0; node=node->next) { 930*7c478bd9Sstevel@tonic-gate if(node->group == glh->group) 931*7c478bd9Sstevel@tonic-gate offset--; 932*7c478bd9Sstevel@tonic-gate }; 933*7c478bd9Sstevel@tonic-gate } else { 934*7c478bd9Sstevel@tonic-gate for(node=glh->recall; node && offset != 0; node=node->prev) { 935*7c478bd9Sstevel@tonic-gate if(node->group == glh->group) 936*7c478bd9Sstevel@tonic-gate offset++; 937*7c478bd9Sstevel@tonic-gate }; 938*7c478bd9Sstevel@tonic-gate }; 939*7c478bd9Sstevel@tonic-gate return node ? node->id : 0; 940*7c478bd9Sstevel@tonic-gate } 941*7c478bd9Sstevel@tonic-gate 942*7c478bd9Sstevel@tonic-gate /*....................................................................... 943*7c478bd9Sstevel@tonic-gate * Recall a line by its history buffer ID. If the line is no longer 944*7c478bd9Sstevel@tonic-gate * in the buffer, or the id is zero, NULL is returned. 945*7c478bd9Sstevel@tonic-gate * 946*7c478bd9Sstevel@tonic-gate * Input: 947*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 948*7c478bd9Sstevel@tonic-gate * id GlhLineID The ID of the line to be returned. 949*7c478bd9Sstevel@tonic-gate * line char * The input line buffer. On input this should contain 950*7c478bd9Sstevel@tonic-gate * the current input line, and on output, its contents 951*7c478bd9Sstevel@tonic-gate * will have been replaced with the saved line. 952*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimensions of the line buffer. 953*7c478bd9Sstevel@tonic-gate * Output: 954*7c478bd9Sstevel@tonic-gate * return char * A pointer to line[0], or NULL if not found. 955*7c478bd9Sstevel@tonic-gate */ 956*7c478bd9Sstevel@tonic-gate char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim) 957*7c478bd9Sstevel@tonic-gate { 958*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line location node being checked */ 959*7c478bd9Sstevel@tonic-gate /* 960*7c478bd9Sstevel@tonic-gate * Is history enabled? 961*7c478bd9Sstevel@tonic-gate */ 962*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->buffer || glh->max_lines == 0) 963*7c478bd9Sstevel@tonic-gate return NULL; 964*7c478bd9Sstevel@tonic-gate /* 965*7c478bd9Sstevel@tonic-gate * Preserve the input line if needed. 966*7c478bd9Sstevel@tonic-gate */ 967*7c478bd9Sstevel@tonic-gate if(_glh_prepare_for_recall(glh, line)) 968*7c478bd9Sstevel@tonic-gate return NULL; 969*7c478bd9Sstevel@tonic-gate /* 970*7c478bd9Sstevel@tonic-gate * Search for the specified line. 971*7c478bd9Sstevel@tonic-gate */ 972*7c478bd9Sstevel@tonic-gate node = _glh_find_id(glh, id); 973*7c478bd9Sstevel@tonic-gate /* 974*7c478bd9Sstevel@tonic-gate * Not found? 975*7c478bd9Sstevel@tonic-gate */ 976*7c478bd9Sstevel@tonic-gate if(!node || node->group != glh->group) 977*7c478bd9Sstevel@tonic-gate return NULL; 978*7c478bd9Sstevel@tonic-gate /* 979*7c478bd9Sstevel@tonic-gate * Record the node of the matching line as the starting point 980*7c478bd9Sstevel@tonic-gate * for subsequent searches. 981*7c478bd9Sstevel@tonic-gate */ 982*7c478bd9Sstevel@tonic-gate glh->recall = node; 983*7c478bd9Sstevel@tonic-gate /* 984*7c478bd9Sstevel@tonic-gate * Copy the recalled line into the provided line buffer. 985*7c478bd9Sstevel@tonic-gate */ 986*7c478bd9Sstevel@tonic-gate _glh_return_line(node->line, line, dim); 987*7c478bd9Sstevel@tonic-gate return line; 988*7c478bd9Sstevel@tonic-gate } 989*7c478bd9Sstevel@tonic-gate 990*7c478bd9Sstevel@tonic-gate /*....................................................................... 991*7c478bd9Sstevel@tonic-gate * Save the current history in a specified file. 992*7c478bd9Sstevel@tonic-gate * 993*7c478bd9Sstevel@tonic-gate * Input: 994*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 995*7c478bd9Sstevel@tonic-gate * filename const char * The name of the new file to record the 996*7c478bd9Sstevel@tonic-gate * history in. 997*7c478bd9Sstevel@tonic-gate * comment const char * Extra information such as timestamps will 998*7c478bd9Sstevel@tonic-gate * be recorded on a line started with this 999*7c478bd9Sstevel@tonic-gate * string, the idea being that the file can 1000*7c478bd9Sstevel@tonic-gate * double as a command file. Specify "" if 1001*7c478bd9Sstevel@tonic-gate * you don't care. 1002*7c478bd9Sstevel@tonic-gate * max_lines int The maximum number of lines to save, or -1 1003*7c478bd9Sstevel@tonic-gate * to save all of the lines in the history 1004*7c478bd9Sstevel@tonic-gate * list. 1005*7c478bd9Sstevel@tonic-gate * Output: 1006*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1007*7c478bd9Sstevel@tonic-gate * 1 - Error. 1008*7c478bd9Sstevel@tonic-gate */ 1009*7c478bd9Sstevel@tonic-gate int _glh_save_history(GlHistory *glh, const char *filename, const char *comment, 1010*7c478bd9Sstevel@tonic-gate int max_lines) 1011*7c478bd9Sstevel@tonic-gate { 1012*7c478bd9Sstevel@tonic-gate #ifdef WITHOUT_FILE_SYSTEM 1013*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "Can't save history without filesystem access", 1014*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1015*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1016*7c478bd9Sstevel@tonic-gate return 1; 1017*7c478bd9Sstevel@tonic-gate #else 1018*7c478bd9Sstevel@tonic-gate FILE *fp; /* The output file */ 1019*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line being saved */ 1020*7c478bd9Sstevel@tonic-gate GlhLineNode *head; /* The head of the list of lines to be saved */ 1021*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* One segment of a line being saved */ 1022*7c478bd9Sstevel@tonic-gate /* 1023*7c478bd9Sstevel@tonic-gate * Check the arguments. 1024*7c478bd9Sstevel@tonic-gate */ 1025*7c478bd9Sstevel@tonic-gate if(!glh || !filename || !comment) { 1026*7c478bd9Sstevel@tonic-gate if(glh) 1027*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1028*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1029*7c478bd9Sstevel@tonic-gate return 1; 1030*7c478bd9Sstevel@tonic-gate }; 1031*7c478bd9Sstevel@tonic-gate /* 1032*7c478bd9Sstevel@tonic-gate * Attempt to open the specified file. 1033*7c478bd9Sstevel@tonic-gate */ 1034*7c478bd9Sstevel@tonic-gate fp = fopen(filename, "w"); 1035*7c478bd9Sstevel@tonic-gate if(!fp) 1036*7c478bd9Sstevel@tonic-gate return _glh_cant_save_history(glh, "Can't open", filename, NULL); 1037*7c478bd9Sstevel@tonic-gate /* 1038*7c478bd9Sstevel@tonic-gate * If a ceiling on the number of lines to save was specified, count 1039*7c478bd9Sstevel@tonic-gate * that number of lines backwards, to find the first line to be saved. 1040*7c478bd9Sstevel@tonic-gate */ 1041*7c478bd9Sstevel@tonic-gate head = NULL; 1042*7c478bd9Sstevel@tonic-gate if(max_lines >= 0) { 1043*7c478bd9Sstevel@tonic-gate for(head=glh->list.tail; head && --max_lines > 0; head=head->prev) 1044*7c478bd9Sstevel@tonic-gate ; 1045*7c478bd9Sstevel@tonic-gate }; 1046*7c478bd9Sstevel@tonic-gate if(!head) 1047*7c478bd9Sstevel@tonic-gate head = glh->list.head; 1048*7c478bd9Sstevel@tonic-gate /* 1049*7c478bd9Sstevel@tonic-gate * Write the contents of the history buffer to the history file, writing 1050*7c478bd9Sstevel@tonic-gate * associated data such as timestamps, to a line starting with the 1051*7c478bd9Sstevel@tonic-gate * specified comment string. 1052*7c478bd9Sstevel@tonic-gate */ 1053*7c478bd9Sstevel@tonic-gate for(node=head; node; node=node->next) { 1054*7c478bd9Sstevel@tonic-gate /* 1055*7c478bd9Sstevel@tonic-gate * Write peripheral information associated with the line, as a comment. 1056*7c478bd9Sstevel@tonic-gate */ 1057*7c478bd9Sstevel@tonic-gate if(fprintf(fp, "%s ", comment) < 0 || 1058*7c478bd9Sstevel@tonic-gate _glh_write_timestamp(fp, node->timestamp) || 1059*7c478bd9Sstevel@tonic-gate fprintf(fp, " %u\n", node->group) < 0) { 1060*7c478bd9Sstevel@tonic-gate return _glh_cant_save_history(glh, "Error writing", filename, fp); 1061*7c478bd9Sstevel@tonic-gate }; 1062*7c478bd9Sstevel@tonic-gate /* 1063*7c478bd9Sstevel@tonic-gate * Write the history line. 1064*7c478bd9Sstevel@tonic-gate */ 1065*7c478bd9Sstevel@tonic-gate for(seg=node->line->head; seg; seg=seg->next) { 1066*7c478bd9Sstevel@tonic-gate size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s); 1067*7c478bd9Sstevel@tonic-gate if(fwrite(seg->s, sizeof(char), slen, fp) != slen) 1068*7c478bd9Sstevel@tonic-gate return _glh_cant_save_history(glh, "Error writing", filename, fp); 1069*7c478bd9Sstevel@tonic-gate }; 1070*7c478bd9Sstevel@tonic-gate fputc('\n', fp); 1071*7c478bd9Sstevel@tonic-gate }; 1072*7c478bd9Sstevel@tonic-gate /* 1073*7c478bd9Sstevel@tonic-gate * Close the history file. 1074*7c478bd9Sstevel@tonic-gate */ 1075*7c478bd9Sstevel@tonic-gate if(fclose(fp) == EOF) 1076*7c478bd9Sstevel@tonic-gate return _glh_cant_save_history(glh, "Error writing", filename, NULL); 1077*7c478bd9Sstevel@tonic-gate return 0; 1078*7c478bd9Sstevel@tonic-gate #endif 1079*7c478bd9Sstevel@tonic-gate } 1080*7c478bd9Sstevel@tonic-gate 1081*7c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM 1082*7c478bd9Sstevel@tonic-gate /*....................................................................... 1083*7c478bd9Sstevel@tonic-gate * This is a private error return function of _glh_save_history(). It 1084*7c478bd9Sstevel@tonic-gate * composes an error report in the error buffer, composed using 1085*7c478bd9Sstevel@tonic-gate * sprintf("%s %s (%s)", message, filename, strerror(errno)). It then 1086*7c478bd9Sstevel@tonic-gate * closes fp and returns the error return code of _glh_save_history(). 1087*7c478bd9Sstevel@tonic-gate * 1088*7c478bd9Sstevel@tonic-gate * Input: 1089*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1090*7c478bd9Sstevel@tonic-gate * message const char * A message to be followed by the filename. 1091*7c478bd9Sstevel@tonic-gate * filename const char * The name of the offending output file. 1092*7c478bd9Sstevel@tonic-gate * fp FILE * The stream to be closed (send NULL if not 1093*7c478bd9Sstevel@tonic-gate * open). 1094*7c478bd9Sstevel@tonic-gate * Output: 1095*7c478bd9Sstevel@tonic-gate * return int Always 1. 1096*7c478bd9Sstevel@tonic-gate */ 1097*7c478bd9Sstevel@tonic-gate static int _glh_cant_save_history(GlHistory *glh, const char *message, 1098*7c478bd9Sstevel@tonic-gate const char *filename, FILE *fp) 1099*7c478bd9Sstevel@tonic-gate { 1100*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, message, filename, " (", 1101*7c478bd9Sstevel@tonic-gate strerror(errno), ")", END_ERR_MSG); 1102*7c478bd9Sstevel@tonic-gate if(fp) 1103*7c478bd9Sstevel@tonic-gate (void) fclose(fp); 1104*7c478bd9Sstevel@tonic-gate return 1; 1105*7c478bd9Sstevel@tonic-gate } 1106*7c478bd9Sstevel@tonic-gate 1107*7c478bd9Sstevel@tonic-gate /*....................................................................... 1108*7c478bd9Sstevel@tonic-gate * Write a timestamp to a given stdio stream, in the format 1109*7c478bd9Sstevel@tonic-gate * yyyymmddhhmmss 1110*7c478bd9Sstevel@tonic-gate * 1111*7c478bd9Sstevel@tonic-gate * Input: 1112*7c478bd9Sstevel@tonic-gate * fp FILE * The stream to write to. 1113*7c478bd9Sstevel@tonic-gate * timestamp time_t The timestamp to be written. 1114*7c478bd9Sstevel@tonic-gate * Output: 1115*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1116*7c478bd9Sstevel@tonic-gate * 1 - Error. 1117*7c478bd9Sstevel@tonic-gate */ 1118*7c478bd9Sstevel@tonic-gate static int _glh_write_timestamp(FILE *fp, time_t timestamp) 1119*7c478bd9Sstevel@tonic-gate { 1120*7c478bd9Sstevel@tonic-gate struct tm *t; /* THe broken-down calendar time */ 1121*7c478bd9Sstevel@tonic-gate /* 1122*7c478bd9Sstevel@tonic-gate * Get the calendar components corresponding to the given timestamp. 1123*7c478bd9Sstevel@tonic-gate */ 1124*7c478bd9Sstevel@tonic-gate if(timestamp < 0 || (t = localtime(×tamp)) == NULL) { 1125*7c478bd9Sstevel@tonic-gate if(fprintf(fp, "?") < 0) 1126*7c478bd9Sstevel@tonic-gate return 1; 1127*7c478bd9Sstevel@tonic-gate return 0; 1128*7c478bd9Sstevel@tonic-gate }; 1129*7c478bd9Sstevel@tonic-gate /* 1130*7c478bd9Sstevel@tonic-gate * Write the calendar time as yyyymmddhhmmss. 1131*7c478bd9Sstevel@tonic-gate */ 1132*7c478bd9Sstevel@tonic-gate if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1, 1133*7c478bd9Sstevel@tonic-gate t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0) 1134*7c478bd9Sstevel@tonic-gate return 1; 1135*7c478bd9Sstevel@tonic-gate return 0; 1136*7c478bd9Sstevel@tonic-gate } 1137*7c478bd9Sstevel@tonic-gate 1138*7c478bd9Sstevel@tonic-gate #endif 1139*7c478bd9Sstevel@tonic-gate 1140*7c478bd9Sstevel@tonic-gate /*....................................................................... 1141*7c478bd9Sstevel@tonic-gate * Restore previous history lines from a given file. 1142*7c478bd9Sstevel@tonic-gate * 1143*7c478bd9Sstevel@tonic-gate * Input: 1144*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1145*7c478bd9Sstevel@tonic-gate * filename const char * The name of the file to read from. 1146*7c478bd9Sstevel@tonic-gate * comment const char * The same comment string that was passed to 1147*7c478bd9Sstevel@tonic-gate * _glh_save_history() when this file was 1148*7c478bd9Sstevel@tonic-gate * written. 1149*7c478bd9Sstevel@tonic-gate * line char * A buffer into which lines can be read. 1150*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimension of line[]. 1151*7c478bd9Sstevel@tonic-gate * Output: 1152*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1153*7c478bd9Sstevel@tonic-gate * 1 - Error. 1154*7c478bd9Sstevel@tonic-gate */ 1155*7c478bd9Sstevel@tonic-gate int _glh_load_history(GlHistory *glh, const char *filename, const char *comment, 1156*7c478bd9Sstevel@tonic-gate char *line, size_t dim) 1157*7c478bd9Sstevel@tonic-gate { 1158*7c478bd9Sstevel@tonic-gate #ifdef WITHOUT_FILE_SYSTEM 1159*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "Can't load history without filesystem access", 1160*7c478bd9Sstevel@tonic-gate END_ERR_MSG); 1161*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1162*7c478bd9Sstevel@tonic-gate return 1; 1163*7c478bd9Sstevel@tonic-gate #else 1164*7c478bd9Sstevel@tonic-gate FILE *fp; /* The output file */ 1165*7c478bd9Sstevel@tonic-gate size_t comment_len; /* The length of the comment string */ 1166*7c478bd9Sstevel@tonic-gate time_t timestamp; /* The timestamp of the history line */ 1167*7c478bd9Sstevel@tonic-gate unsigned group; /* The identifier of the history group to which */ 1168*7c478bd9Sstevel@tonic-gate /* the line belongs. */ 1169*7c478bd9Sstevel@tonic-gate int lineno; /* The line number being read */ 1170*7c478bd9Sstevel@tonic-gate /* 1171*7c478bd9Sstevel@tonic-gate * Check the arguments. 1172*7c478bd9Sstevel@tonic-gate */ 1173*7c478bd9Sstevel@tonic-gate if(!glh || !filename || !comment || !line) { 1174*7c478bd9Sstevel@tonic-gate if(glh) 1175*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1176*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1177*7c478bd9Sstevel@tonic-gate return 1; 1178*7c478bd9Sstevel@tonic-gate }; 1179*7c478bd9Sstevel@tonic-gate /* 1180*7c478bd9Sstevel@tonic-gate * Measure the length of the comment string. 1181*7c478bd9Sstevel@tonic-gate */ 1182*7c478bd9Sstevel@tonic-gate comment_len = strlen(comment); 1183*7c478bd9Sstevel@tonic-gate /* 1184*7c478bd9Sstevel@tonic-gate * Clear the history list. 1185*7c478bd9Sstevel@tonic-gate */ 1186*7c478bd9Sstevel@tonic-gate _glh_clear_history(glh, 1); 1187*7c478bd9Sstevel@tonic-gate /* 1188*7c478bd9Sstevel@tonic-gate * Attempt to open the specified file. Don't treat it as an error 1189*7c478bd9Sstevel@tonic-gate * if the file doesn't exist. 1190*7c478bd9Sstevel@tonic-gate */ 1191*7c478bd9Sstevel@tonic-gate fp = fopen(filename, "r"); 1192*7c478bd9Sstevel@tonic-gate if(!fp) 1193*7c478bd9Sstevel@tonic-gate return 0; 1194*7c478bd9Sstevel@tonic-gate /* 1195*7c478bd9Sstevel@tonic-gate * Attempt to read each line and preceding peripheral info, and add these 1196*7c478bd9Sstevel@tonic-gate * to the history list. 1197*7c478bd9Sstevel@tonic-gate */ 1198*7c478bd9Sstevel@tonic-gate for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) { 1199*7c478bd9Sstevel@tonic-gate char *lptr; /* A pointer into the input line */ 1200*7c478bd9Sstevel@tonic-gate /* 1201*7c478bd9Sstevel@tonic-gate * Check that the line starts with the comment string. 1202*7c478bd9Sstevel@tonic-gate */ 1203*7c478bd9Sstevel@tonic-gate if(strncmp(line, comment, comment_len) != 0) { 1204*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, 1205*7c478bd9Sstevel@tonic-gate "Corrupt history parameter line", fp); 1206*7c478bd9Sstevel@tonic-gate }; 1207*7c478bd9Sstevel@tonic-gate /* 1208*7c478bd9Sstevel@tonic-gate * Skip spaces and tabs after the comment. 1209*7c478bd9Sstevel@tonic-gate */ 1210*7c478bd9Sstevel@tonic-gate for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++) 1211*7c478bd9Sstevel@tonic-gate ; 1212*7c478bd9Sstevel@tonic-gate /* 1213*7c478bd9Sstevel@tonic-gate * The next word must be a timestamp. 1214*7c478bd9Sstevel@tonic-gate */ 1215*7c478bd9Sstevel@tonic-gate if(_glh_decode_timestamp(lptr, &lptr, ×tamp)) { 1216*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, 1217*7c478bd9Sstevel@tonic-gate "Corrupt timestamp", fp); 1218*7c478bd9Sstevel@tonic-gate }; 1219*7c478bd9Sstevel@tonic-gate /* 1220*7c478bd9Sstevel@tonic-gate * Skip spaces and tabs. 1221*7c478bd9Sstevel@tonic-gate */ 1222*7c478bd9Sstevel@tonic-gate while(*lptr==' ' || *lptr=='\t') 1223*7c478bd9Sstevel@tonic-gate lptr++; 1224*7c478bd9Sstevel@tonic-gate /* 1225*7c478bd9Sstevel@tonic-gate * The next word must be an unsigned integer group number. 1226*7c478bd9Sstevel@tonic-gate */ 1227*7c478bd9Sstevel@tonic-gate group = (int) strtoul(lptr, &lptr, 10); 1228*7c478bd9Sstevel@tonic-gate if(*lptr != ' ' && *lptr != '\n') { 1229*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, 1230*7c478bd9Sstevel@tonic-gate "Corrupt group id", fp); 1231*7c478bd9Sstevel@tonic-gate }; 1232*7c478bd9Sstevel@tonic-gate /* 1233*7c478bd9Sstevel@tonic-gate * Skip spaces and tabs. 1234*7c478bd9Sstevel@tonic-gate */ 1235*7c478bd9Sstevel@tonic-gate while(*lptr==' ' || *lptr=='\t') 1236*7c478bd9Sstevel@tonic-gate lptr++; 1237*7c478bd9Sstevel@tonic-gate /* 1238*7c478bd9Sstevel@tonic-gate * There shouldn't be anything left on the line. 1239*7c478bd9Sstevel@tonic-gate */ 1240*7c478bd9Sstevel@tonic-gate if(*lptr != '\n') { 1241*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, 1242*7c478bd9Sstevel@tonic-gate "Corrupt parameter line", fp); 1243*7c478bd9Sstevel@tonic-gate }; 1244*7c478bd9Sstevel@tonic-gate /* 1245*7c478bd9Sstevel@tonic-gate * Now read the history line itself. 1246*7c478bd9Sstevel@tonic-gate */ 1247*7c478bd9Sstevel@tonic-gate lineno++; 1248*7c478bd9Sstevel@tonic-gate if(fgets(line, dim, fp) == NULL) 1249*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, "Read error", fp); 1250*7c478bd9Sstevel@tonic-gate /* 1251*7c478bd9Sstevel@tonic-gate * Append the line to the history buffer. 1252*7c478bd9Sstevel@tonic-gate */ 1253*7c478bd9Sstevel@tonic-gate if(_glh_add_history(glh, line, 1)) { 1254*7c478bd9Sstevel@tonic-gate return _glh_cant_load_history(glh, filename, lineno, 1255*7c478bd9Sstevel@tonic-gate "Insufficient memory to record line", fp); 1256*7c478bd9Sstevel@tonic-gate }; 1257*7c478bd9Sstevel@tonic-gate /* 1258*7c478bd9Sstevel@tonic-gate * Record the group and timestamp information along with the line. 1259*7c478bd9Sstevel@tonic-gate */ 1260*7c478bd9Sstevel@tonic-gate if(glh->list.tail) { 1261*7c478bd9Sstevel@tonic-gate glh->list.tail->timestamp = timestamp; 1262*7c478bd9Sstevel@tonic-gate glh->list.tail->group = group; 1263*7c478bd9Sstevel@tonic-gate }; 1264*7c478bd9Sstevel@tonic-gate }; 1265*7c478bd9Sstevel@tonic-gate /* 1266*7c478bd9Sstevel@tonic-gate * Close the file. 1267*7c478bd9Sstevel@tonic-gate */ 1268*7c478bd9Sstevel@tonic-gate (void) fclose(fp); 1269*7c478bd9Sstevel@tonic-gate return 0; 1270*7c478bd9Sstevel@tonic-gate #endif 1271*7c478bd9Sstevel@tonic-gate } 1272*7c478bd9Sstevel@tonic-gate 1273*7c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM 1274*7c478bd9Sstevel@tonic-gate /*....................................................................... 1275*7c478bd9Sstevel@tonic-gate * This is a private error return function of _glh_load_history(). 1276*7c478bd9Sstevel@tonic-gate */ 1277*7c478bd9Sstevel@tonic-gate static int _glh_cant_load_history(GlHistory *glh, const char *filename, 1278*7c478bd9Sstevel@tonic-gate int lineno, const char *message, FILE *fp) 1279*7c478bd9Sstevel@tonic-gate { 1280*7c478bd9Sstevel@tonic-gate char lnum[20]; 1281*7c478bd9Sstevel@tonic-gate /* 1282*7c478bd9Sstevel@tonic-gate * Convert the line number to a string. 1283*7c478bd9Sstevel@tonic-gate */ 1284*7c478bd9Sstevel@tonic-gate snprintf(lnum, sizeof(lnum), "%d", lineno); 1285*7c478bd9Sstevel@tonic-gate /* 1286*7c478bd9Sstevel@tonic-gate * Render an error message. 1287*7c478bd9Sstevel@tonic-gate */ 1288*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG); 1289*7c478bd9Sstevel@tonic-gate /* 1290*7c478bd9Sstevel@tonic-gate * Close the file. 1291*7c478bd9Sstevel@tonic-gate */ 1292*7c478bd9Sstevel@tonic-gate if(fp) 1293*7c478bd9Sstevel@tonic-gate (void) fclose(fp); 1294*7c478bd9Sstevel@tonic-gate return 1; 1295*7c478bd9Sstevel@tonic-gate } 1296*7c478bd9Sstevel@tonic-gate 1297*7c478bd9Sstevel@tonic-gate /*....................................................................... 1298*7c478bd9Sstevel@tonic-gate * Read a timestamp from a string. 1299*7c478bd9Sstevel@tonic-gate * 1300*7c478bd9Sstevel@tonic-gate * Input: 1301*7c478bd9Sstevel@tonic-gate * string char * The string to read from. 1302*7c478bd9Sstevel@tonic-gate * Input/Output: 1303*7c478bd9Sstevel@tonic-gate * endp char ** On output *endp will point to the next unprocessed 1304*7c478bd9Sstevel@tonic-gate * character in string[]. 1305*7c478bd9Sstevel@tonic-gate * timestamp time_t * The timestamp will be assigned to *t. 1306*7c478bd9Sstevel@tonic-gate * Output: 1307*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1308*7c478bd9Sstevel@tonic-gate * 1 - Error. 1309*7c478bd9Sstevel@tonic-gate */ 1310*7c478bd9Sstevel@tonic-gate static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp) 1311*7c478bd9Sstevel@tonic-gate { 1312*7c478bd9Sstevel@tonic-gate unsigned year,month,day,hour,min,sec; /* Calendar time components */ 1313*7c478bd9Sstevel@tonic-gate struct tm t; 1314*7c478bd9Sstevel@tonic-gate /* 1315*7c478bd9Sstevel@tonic-gate * There are 14 characters in the date format yyyymmddhhmmss. 1316*7c478bd9Sstevel@tonic-gate */ 1317*7c478bd9Sstevel@tonic-gate enum {TSLEN=14}; 1318*7c478bd9Sstevel@tonic-gate char timestr[TSLEN+1]; /* The timestamp part of the string */ 1319*7c478bd9Sstevel@tonic-gate /* 1320*7c478bd9Sstevel@tonic-gate * If the time wasn't available at the time that the line was recorded 1321*7c478bd9Sstevel@tonic-gate * it will have been written as "?". Check for this before trying 1322*7c478bd9Sstevel@tonic-gate * to read the timestamp. 1323*7c478bd9Sstevel@tonic-gate */ 1324*7c478bd9Sstevel@tonic-gate if(string[0] == '\?') { 1325*7c478bd9Sstevel@tonic-gate *endp = string+1; 1326*7c478bd9Sstevel@tonic-gate *timestamp = -1; 1327*7c478bd9Sstevel@tonic-gate return 0; 1328*7c478bd9Sstevel@tonic-gate }; 1329*7c478bd9Sstevel@tonic-gate /* 1330*7c478bd9Sstevel@tonic-gate * The timestamp is expected to be written in the form yyyymmddhhmmss. 1331*7c478bd9Sstevel@tonic-gate */ 1332*7c478bd9Sstevel@tonic-gate if(strlen(string) < TSLEN) { 1333*7c478bd9Sstevel@tonic-gate *endp = string; 1334*7c478bd9Sstevel@tonic-gate return 1; 1335*7c478bd9Sstevel@tonic-gate }; 1336*7c478bd9Sstevel@tonic-gate /* 1337*7c478bd9Sstevel@tonic-gate * Copy the timestamp out of the string. 1338*7c478bd9Sstevel@tonic-gate */ 1339*7c478bd9Sstevel@tonic-gate strncpy(timestr, string, TSLEN); 1340*7c478bd9Sstevel@tonic-gate timestr[TSLEN] = '\0'; 1341*7c478bd9Sstevel@tonic-gate /* 1342*7c478bd9Sstevel@tonic-gate * Decode the timestamp. 1343*7c478bd9Sstevel@tonic-gate */ 1344*7c478bd9Sstevel@tonic-gate if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min, 1345*7c478bd9Sstevel@tonic-gate &sec) != 6) { 1346*7c478bd9Sstevel@tonic-gate *endp = string; 1347*7c478bd9Sstevel@tonic-gate return 1; 1348*7c478bd9Sstevel@tonic-gate }; 1349*7c478bd9Sstevel@tonic-gate /* 1350*7c478bd9Sstevel@tonic-gate * Advance the string pointer over the successfully read timestamp. 1351*7c478bd9Sstevel@tonic-gate */ 1352*7c478bd9Sstevel@tonic-gate *endp = string + TSLEN; 1353*7c478bd9Sstevel@tonic-gate /* 1354*7c478bd9Sstevel@tonic-gate * Copy the read values into a struct tm. 1355*7c478bd9Sstevel@tonic-gate */ 1356*7c478bd9Sstevel@tonic-gate t.tm_sec = sec; 1357*7c478bd9Sstevel@tonic-gate t.tm_min = min; 1358*7c478bd9Sstevel@tonic-gate t.tm_hour = hour; 1359*7c478bd9Sstevel@tonic-gate t.tm_mday = day; 1360*7c478bd9Sstevel@tonic-gate t.tm_wday = 0; 1361*7c478bd9Sstevel@tonic-gate t.tm_yday = 0; 1362*7c478bd9Sstevel@tonic-gate t.tm_mon = month - 1; 1363*7c478bd9Sstevel@tonic-gate t.tm_year = year - 1900; 1364*7c478bd9Sstevel@tonic-gate t.tm_isdst = -1; 1365*7c478bd9Sstevel@tonic-gate /* 1366*7c478bd9Sstevel@tonic-gate * Convert the contents of the struct tm to a time_t. 1367*7c478bd9Sstevel@tonic-gate */ 1368*7c478bd9Sstevel@tonic-gate *timestamp = mktime(&t); 1369*7c478bd9Sstevel@tonic-gate return 0; 1370*7c478bd9Sstevel@tonic-gate } 1371*7c478bd9Sstevel@tonic-gate #endif 1372*7c478bd9Sstevel@tonic-gate 1373*7c478bd9Sstevel@tonic-gate /*....................................................................... 1374*7c478bd9Sstevel@tonic-gate * Switch history groups. 1375*7c478bd9Sstevel@tonic-gate * 1376*7c478bd9Sstevel@tonic-gate * Input: 1377*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1378*7c478bd9Sstevel@tonic-gate * group unsigned The new group identifier. This will be recorded 1379*7c478bd9Sstevel@tonic-gate * with subsequent history lines, and subsequent 1380*7c478bd9Sstevel@tonic-gate * history searches will only return lines with 1381*7c478bd9Sstevel@tonic-gate * this group identifier. This allows multiple 1382*7c478bd9Sstevel@tonic-gate * separate history lists to exist within 1383*7c478bd9Sstevel@tonic-gate * a single GlHistory object. Note that the 1384*7c478bd9Sstevel@tonic-gate * default group identifier is 0. 1385*7c478bd9Sstevel@tonic-gate * Output: 1386*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1387*7c478bd9Sstevel@tonic-gate * 1 - Error. 1388*7c478bd9Sstevel@tonic-gate */ 1389*7c478bd9Sstevel@tonic-gate int _glh_set_group(GlHistory *glh, unsigned group) 1390*7c478bd9Sstevel@tonic-gate { 1391*7c478bd9Sstevel@tonic-gate /* 1392*7c478bd9Sstevel@tonic-gate * Check the arguments. 1393*7c478bd9Sstevel@tonic-gate */ 1394*7c478bd9Sstevel@tonic-gate if(!glh) { 1395*7c478bd9Sstevel@tonic-gate if(glh) 1396*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1397*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1398*7c478bd9Sstevel@tonic-gate return 1; 1399*7c478bd9Sstevel@tonic-gate }; 1400*7c478bd9Sstevel@tonic-gate /* 1401*7c478bd9Sstevel@tonic-gate * Is the group being changed? 1402*7c478bd9Sstevel@tonic-gate */ 1403*7c478bd9Sstevel@tonic-gate if(group != glh->group) { 1404*7c478bd9Sstevel@tonic-gate /* 1405*7c478bd9Sstevel@tonic-gate * Cancel any ongoing search. 1406*7c478bd9Sstevel@tonic-gate */ 1407*7c478bd9Sstevel@tonic-gate if(_glh_cancel_search(glh)) 1408*7c478bd9Sstevel@tonic-gate return 1; 1409*7c478bd9Sstevel@tonic-gate /* 1410*7c478bd9Sstevel@tonic-gate * Record the new group. 1411*7c478bd9Sstevel@tonic-gate */ 1412*7c478bd9Sstevel@tonic-gate glh->group = group; 1413*7c478bd9Sstevel@tonic-gate }; 1414*7c478bd9Sstevel@tonic-gate return 0; 1415*7c478bd9Sstevel@tonic-gate } 1416*7c478bd9Sstevel@tonic-gate 1417*7c478bd9Sstevel@tonic-gate /*....................................................................... 1418*7c478bd9Sstevel@tonic-gate * Query the current history group. 1419*7c478bd9Sstevel@tonic-gate * 1420*7c478bd9Sstevel@tonic-gate * Input: 1421*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1422*7c478bd9Sstevel@tonic-gate * Output: 1423*7c478bd9Sstevel@tonic-gate * return unsigned The group identifier. 1424*7c478bd9Sstevel@tonic-gate */ 1425*7c478bd9Sstevel@tonic-gate int _glh_get_group(GlHistory *glh) 1426*7c478bd9Sstevel@tonic-gate { 1427*7c478bd9Sstevel@tonic-gate return glh ? glh->group : 0; 1428*7c478bd9Sstevel@tonic-gate } 1429*7c478bd9Sstevel@tonic-gate 1430*7c478bd9Sstevel@tonic-gate /*....................................................................... 1431*7c478bd9Sstevel@tonic-gate * Display the contents of the history list. 1432*7c478bd9Sstevel@tonic-gate * 1433*7c478bd9Sstevel@tonic-gate * Input: 1434*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1435*7c478bd9Sstevel@tonic-gate * write_fn GlWriteFn * The function to call to write the line, or 1436*7c478bd9Sstevel@tonic-gate * 0 to discard the output. 1437*7c478bd9Sstevel@tonic-gate * data void * Anonymous data to pass to write_fn(). 1438*7c478bd9Sstevel@tonic-gate * fmt const char * A format string. This can contain arbitrary 1439*7c478bd9Sstevel@tonic-gate * characters, which are written verbatim, plus 1440*7c478bd9Sstevel@tonic-gate * any of the following format directives: 1441*7c478bd9Sstevel@tonic-gate * %D - The date, like 2001-11-20 1442*7c478bd9Sstevel@tonic-gate * %T - The time of day, like 23:59:59 1443*7c478bd9Sstevel@tonic-gate * %N - The sequential entry number of the 1444*7c478bd9Sstevel@tonic-gate * line in the history buffer. 1445*7c478bd9Sstevel@tonic-gate * %G - The history group number of the line. 1446*7c478bd9Sstevel@tonic-gate * %% - A literal % character. 1447*7c478bd9Sstevel@tonic-gate * %H - The history line. 1448*7c478bd9Sstevel@tonic-gate * all_groups int If true, display history lines from all 1449*7c478bd9Sstevel@tonic-gate * history groups. Otherwise only display 1450*7c478bd9Sstevel@tonic-gate * those of the current history group. 1451*7c478bd9Sstevel@tonic-gate * max_lines int If max_lines is < 0, all available lines 1452*7c478bd9Sstevel@tonic-gate * are displayed. Otherwise only the most 1453*7c478bd9Sstevel@tonic-gate * recent max_lines lines will be displayed. 1454*7c478bd9Sstevel@tonic-gate * Output: 1455*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1456*7c478bd9Sstevel@tonic-gate * 1 - Error. 1457*7c478bd9Sstevel@tonic-gate */ 1458*7c478bd9Sstevel@tonic-gate int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data, 1459*7c478bd9Sstevel@tonic-gate const char *fmt, int all_groups, int max_lines) 1460*7c478bd9Sstevel@tonic-gate { 1461*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line being displayed */ 1462*7c478bd9Sstevel@tonic-gate GlhLineNode *oldest; /* The oldest line to display */ 1463*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* One segment of a line being displayed */ 1464*7c478bd9Sstevel@tonic-gate enum {TSMAX=32}; /* The maximum length of the date and time string */ 1465*7c478bd9Sstevel@tonic-gate char buffer[TSMAX+1]; /* The buffer in which to write the date and time */ 1466*7c478bd9Sstevel@tonic-gate int idlen; /* The length of displayed ID strings */ 1467*7c478bd9Sstevel@tonic-gate unsigned grpmax; /* The maximum group number in the buffer */ 1468*7c478bd9Sstevel@tonic-gate int grplen; /* The number of characters needed to print grpmax */ 1469*7c478bd9Sstevel@tonic-gate int len; /* The length of a string to be written */ 1470*7c478bd9Sstevel@tonic-gate /* 1471*7c478bd9Sstevel@tonic-gate * Check the arguments. 1472*7c478bd9Sstevel@tonic-gate */ 1473*7c478bd9Sstevel@tonic-gate if(!glh || !write_fn || !fmt) { 1474*7c478bd9Sstevel@tonic-gate if(glh) 1475*7c478bd9Sstevel@tonic-gate _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG); 1476*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1477*7c478bd9Sstevel@tonic-gate return 1; 1478*7c478bd9Sstevel@tonic-gate }; 1479*7c478bd9Sstevel@tonic-gate /* 1480*7c478bd9Sstevel@tonic-gate * Is history enabled? 1481*7c478bd9Sstevel@tonic-gate */ 1482*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->list.head) 1483*7c478bd9Sstevel@tonic-gate return 0; 1484*7c478bd9Sstevel@tonic-gate /* 1485*7c478bd9Sstevel@tonic-gate * Work out the length to display ID numbers, choosing the length of 1486*7c478bd9Sstevel@tonic-gate * the biggest number in the buffer. Smaller numbers will be padded 1487*7c478bd9Sstevel@tonic-gate * with leading zeroes if needed. 1488*7c478bd9Sstevel@tonic-gate */ 1489*7c478bd9Sstevel@tonic-gate snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) glh->list.tail->id); 1490*7c478bd9Sstevel@tonic-gate idlen = strlen(buffer); 1491*7c478bd9Sstevel@tonic-gate /* 1492*7c478bd9Sstevel@tonic-gate * Find the largest group number. 1493*7c478bd9Sstevel@tonic-gate */ 1494*7c478bd9Sstevel@tonic-gate grpmax = 0; 1495*7c478bd9Sstevel@tonic-gate for(node=glh->list.head; node; node=node->next) { 1496*7c478bd9Sstevel@tonic-gate if(node->group > grpmax) 1497*7c478bd9Sstevel@tonic-gate grpmax = node->group; 1498*7c478bd9Sstevel@tonic-gate }; 1499*7c478bd9Sstevel@tonic-gate /* 1500*7c478bd9Sstevel@tonic-gate * Find out how many characters are needed to display the group number. 1501*7c478bd9Sstevel@tonic-gate */ 1502*7c478bd9Sstevel@tonic-gate snprintf(buffer, sizeof(buffer), "%u", (unsigned) grpmax); 1503*7c478bd9Sstevel@tonic-gate grplen = strlen(buffer); 1504*7c478bd9Sstevel@tonic-gate /* 1505*7c478bd9Sstevel@tonic-gate * Find the node that follows the oldest line to be displayed. 1506*7c478bd9Sstevel@tonic-gate */ 1507*7c478bd9Sstevel@tonic-gate if(max_lines < 0) { 1508*7c478bd9Sstevel@tonic-gate oldest = glh->list.head; 1509*7c478bd9Sstevel@tonic-gate } else if(max_lines==0) { 1510*7c478bd9Sstevel@tonic-gate return 0; 1511*7c478bd9Sstevel@tonic-gate } else { 1512*7c478bd9Sstevel@tonic-gate for(oldest=glh->list.tail; oldest; oldest=oldest->prev) { 1513*7c478bd9Sstevel@tonic-gate if((all_groups || oldest->group == glh->group) && --max_lines <= 0) 1514*7c478bd9Sstevel@tonic-gate break; 1515*7c478bd9Sstevel@tonic-gate }; 1516*7c478bd9Sstevel@tonic-gate /* 1517*7c478bd9Sstevel@tonic-gate * If the number of lines in the buffer doesn't exceed the specified 1518*7c478bd9Sstevel@tonic-gate * maximum, start from the oldest line in the buffer. 1519*7c478bd9Sstevel@tonic-gate */ 1520*7c478bd9Sstevel@tonic-gate if(!oldest) 1521*7c478bd9Sstevel@tonic-gate oldest = glh->list.head; 1522*7c478bd9Sstevel@tonic-gate }; 1523*7c478bd9Sstevel@tonic-gate /* 1524*7c478bd9Sstevel@tonic-gate * List the history lines in increasing time order. 1525*7c478bd9Sstevel@tonic-gate */ 1526*7c478bd9Sstevel@tonic-gate for(node=oldest; node; node=node->next) { 1527*7c478bd9Sstevel@tonic-gate /* 1528*7c478bd9Sstevel@tonic-gate * Only display lines from the current history group, unless 1529*7c478bd9Sstevel@tonic-gate * told otherwise. 1530*7c478bd9Sstevel@tonic-gate */ 1531*7c478bd9Sstevel@tonic-gate if(all_groups || node->group == glh->group) { 1532*7c478bd9Sstevel@tonic-gate const char *fptr; /* A pointer into the format string */ 1533*7c478bd9Sstevel@tonic-gate struct tm *t = NULL; /* The broken time version of the timestamp */ 1534*7c478bd9Sstevel@tonic-gate /* 1535*7c478bd9Sstevel@tonic-gate * Work out the calendar representation of the node timestamp. 1536*7c478bd9Sstevel@tonic-gate */ 1537*7c478bd9Sstevel@tonic-gate if(node->timestamp != (time_t) -1) 1538*7c478bd9Sstevel@tonic-gate t = localtime(&node->timestamp); 1539*7c478bd9Sstevel@tonic-gate /* 1540*7c478bd9Sstevel@tonic-gate * Parse the format string. 1541*7c478bd9Sstevel@tonic-gate */ 1542*7c478bd9Sstevel@tonic-gate fptr = fmt; 1543*7c478bd9Sstevel@tonic-gate while(*fptr) { 1544*7c478bd9Sstevel@tonic-gate /* 1545*7c478bd9Sstevel@tonic-gate * Search for the start of the next format directive or the end of the string. 1546*7c478bd9Sstevel@tonic-gate */ 1547*7c478bd9Sstevel@tonic-gate const char *start = fptr; 1548*7c478bd9Sstevel@tonic-gate while(*fptr && *fptr != '%') 1549*7c478bd9Sstevel@tonic-gate fptr++; 1550*7c478bd9Sstevel@tonic-gate /* 1551*7c478bd9Sstevel@tonic-gate * Display any literal characters that precede the located directive. 1552*7c478bd9Sstevel@tonic-gate */ 1553*7c478bd9Sstevel@tonic-gate if(fptr > start) { 1554*7c478bd9Sstevel@tonic-gate len = (int) (fptr - start); 1555*7c478bd9Sstevel@tonic-gate if(write_fn(data, start, len) != len) 1556*7c478bd9Sstevel@tonic-gate return 1; 1557*7c478bd9Sstevel@tonic-gate }; 1558*7c478bd9Sstevel@tonic-gate /* 1559*7c478bd9Sstevel@tonic-gate * Did we hit a new directive before the end of the line? 1560*7c478bd9Sstevel@tonic-gate */ 1561*7c478bd9Sstevel@tonic-gate if(*fptr) { 1562*7c478bd9Sstevel@tonic-gate /* 1563*7c478bd9Sstevel@tonic-gate * Obey the directive. Ignore unknown directives. 1564*7c478bd9Sstevel@tonic-gate */ 1565*7c478bd9Sstevel@tonic-gate switch(*++fptr) { 1566*7c478bd9Sstevel@tonic-gate case 'D': /* Display the date */ 1567*7c478bd9Sstevel@tonic-gate if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) { 1568*7c478bd9Sstevel@tonic-gate len = strlen(buffer); 1569*7c478bd9Sstevel@tonic-gate if(write_fn(data, buffer, len) != len) 1570*7c478bd9Sstevel@tonic-gate return 1; 1571*7c478bd9Sstevel@tonic-gate }; 1572*7c478bd9Sstevel@tonic-gate break; 1573*7c478bd9Sstevel@tonic-gate case 'T': /* Display the time of day */ 1574*7c478bd9Sstevel@tonic-gate if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) { 1575*7c478bd9Sstevel@tonic-gate len = strlen(buffer); 1576*7c478bd9Sstevel@tonic-gate if(write_fn(data, buffer, len) != len) 1577*7c478bd9Sstevel@tonic-gate return 1; 1578*7c478bd9Sstevel@tonic-gate }; 1579*7c478bd9Sstevel@tonic-gate break; 1580*7c478bd9Sstevel@tonic-gate case 'N': /* Display the sequential entry number */ 1581*7c478bd9Sstevel@tonic-gate snprintf(buffer, sizeof(buffer), "%*lu", idlen, (unsigned long) node->id); 1582*7c478bd9Sstevel@tonic-gate len = strlen(buffer); 1583*7c478bd9Sstevel@tonic-gate if(write_fn(data, buffer, len) != len) 1584*7c478bd9Sstevel@tonic-gate return 1; 1585*7c478bd9Sstevel@tonic-gate break; 1586*7c478bd9Sstevel@tonic-gate case 'G': 1587*7c478bd9Sstevel@tonic-gate snprintf(buffer, sizeof(buffer), "%*u", grplen, (unsigned) node->group); 1588*7c478bd9Sstevel@tonic-gate len = strlen(buffer); 1589*7c478bd9Sstevel@tonic-gate if(write_fn(data, buffer, len) != len) 1590*7c478bd9Sstevel@tonic-gate return 1; 1591*7c478bd9Sstevel@tonic-gate break; 1592*7c478bd9Sstevel@tonic-gate case 'H': /* Display the history line */ 1593*7c478bd9Sstevel@tonic-gate for(seg=node->line->head; seg; seg=seg->next) { 1594*7c478bd9Sstevel@tonic-gate len = seg->next ? GLH_SEG_SIZE : strlen(seg->s); 1595*7c478bd9Sstevel@tonic-gate if(write_fn(data, seg->s, len) != len) 1596*7c478bd9Sstevel@tonic-gate return 1; 1597*7c478bd9Sstevel@tonic-gate }; 1598*7c478bd9Sstevel@tonic-gate break; 1599*7c478bd9Sstevel@tonic-gate case '%': /* A literal % symbol */ 1600*7c478bd9Sstevel@tonic-gate if(write_fn(data, "%", 1) != 1) 1601*7c478bd9Sstevel@tonic-gate return 1; 1602*7c478bd9Sstevel@tonic-gate break; 1603*7c478bd9Sstevel@tonic-gate }; 1604*7c478bd9Sstevel@tonic-gate /* 1605*7c478bd9Sstevel@tonic-gate * Skip the directive. 1606*7c478bd9Sstevel@tonic-gate */ 1607*7c478bd9Sstevel@tonic-gate if(*fptr) 1608*7c478bd9Sstevel@tonic-gate fptr++; 1609*7c478bd9Sstevel@tonic-gate }; 1610*7c478bd9Sstevel@tonic-gate }; 1611*7c478bd9Sstevel@tonic-gate }; 1612*7c478bd9Sstevel@tonic-gate }; 1613*7c478bd9Sstevel@tonic-gate return 0; 1614*7c478bd9Sstevel@tonic-gate } 1615*7c478bd9Sstevel@tonic-gate 1616*7c478bd9Sstevel@tonic-gate /*....................................................................... 1617*7c478bd9Sstevel@tonic-gate * Change the size of the history buffer. 1618*7c478bd9Sstevel@tonic-gate * 1619*7c478bd9Sstevel@tonic-gate * Input: 1620*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1621*7c478bd9Sstevel@tonic-gate * bufsize size_t The number of bytes in the history buffer, or 0 1622*7c478bd9Sstevel@tonic-gate * to delete the buffer completely. 1623*7c478bd9Sstevel@tonic-gate * Output: 1624*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 1625*7c478bd9Sstevel@tonic-gate * 1 - Insufficient memory (the previous buffer 1626*7c478bd9Sstevel@tonic-gate * will have been retained). No error message 1627*7c478bd9Sstevel@tonic-gate * will be displayed. 1628*7c478bd9Sstevel@tonic-gate */ 1629*7c478bd9Sstevel@tonic-gate int _glh_resize_history(GlHistory *glh, size_t bufsize) 1630*7c478bd9Sstevel@tonic-gate { 1631*7c478bd9Sstevel@tonic-gate int nbuff; /* The number of segments in the new buffer */ 1632*7c478bd9Sstevel@tonic-gate int i; 1633*7c478bd9Sstevel@tonic-gate /* 1634*7c478bd9Sstevel@tonic-gate * Check the arguments. 1635*7c478bd9Sstevel@tonic-gate */ 1636*7c478bd9Sstevel@tonic-gate if(!glh) { 1637*7c478bd9Sstevel@tonic-gate errno = EINVAL; 1638*7c478bd9Sstevel@tonic-gate return 1; 1639*7c478bd9Sstevel@tonic-gate }; 1640*7c478bd9Sstevel@tonic-gate /* 1641*7c478bd9Sstevel@tonic-gate * How many buffer segments does the requested buffer size correspond 1642*7c478bd9Sstevel@tonic-gate * to? 1643*7c478bd9Sstevel@tonic-gate */ 1644*7c478bd9Sstevel@tonic-gate nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 1645*7c478bd9Sstevel@tonic-gate /* 1646*7c478bd9Sstevel@tonic-gate * Has a different size than the current size been requested? 1647*7c478bd9Sstevel@tonic-gate */ 1648*7c478bd9Sstevel@tonic-gate if(glh->nbuff != nbuff) { 1649*7c478bd9Sstevel@tonic-gate /* 1650*7c478bd9Sstevel@tonic-gate * Cancel any ongoing search. 1651*7c478bd9Sstevel@tonic-gate */ 1652*7c478bd9Sstevel@tonic-gate (void) _glh_cancel_search(glh); 1653*7c478bd9Sstevel@tonic-gate /* 1654*7c478bd9Sstevel@tonic-gate * Create a wholly new buffer? 1655*7c478bd9Sstevel@tonic-gate */ 1656*7c478bd9Sstevel@tonic-gate if(glh->nbuff == 0 && nbuff>0) { 1657*7c478bd9Sstevel@tonic-gate glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff); 1658*7c478bd9Sstevel@tonic-gate if(!glh->buffer) 1659*7c478bd9Sstevel@tonic-gate return 1; 1660*7c478bd9Sstevel@tonic-gate glh->nbuff = nbuff; 1661*7c478bd9Sstevel@tonic-gate glh->nfree = glh->nbuff; 1662*7c478bd9Sstevel@tonic-gate glh->nbusy = 0; 1663*7c478bd9Sstevel@tonic-gate glh->nline = 0; 1664*7c478bd9Sstevel@tonic-gate /* 1665*7c478bd9Sstevel@tonic-gate * Link the currently unused nodes of the buffer into a list. 1666*7c478bd9Sstevel@tonic-gate */ 1667*7c478bd9Sstevel@tonic-gate glh->unused = glh->buffer; 1668*7c478bd9Sstevel@tonic-gate for(i=0; i<glh->nbuff-1; i++) { 1669*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg = glh->unused + i; 1670*7c478bd9Sstevel@tonic-gate seg->next = seg + 1; 1671*7c478bd9Sstevel@tonic-gate }; 1672*7c478bd9Sstevel@tonic-gate glh->unused[i].next = NULL; 1673*7c478bd9Sstevel@tonic-gate /* 1674*7c478bd9Sstevel@tonic-gate * Delete an existing buffer? 1675*7c478bd9Sstevel@tonic-gate */ 1676*7c478bd9Sstevel@tonic-gate } else if(nbuff == 0) { 1677*7c478bd9Sstevel@tonic-gate _glh_clear_history(glh, 1); 1678*7c478bd9Sstevel@tonic-gate free(glh->buffer); 1679*7c478bd9Sstevel@tonic-gate glh->buffer = NULL; 1680*7c478bd9Sstevel@tonic-gate glh->unused = NULL; 1681*7c478bd9Sstevel@tonic-gate glh->nbuff = 0; 1682*7c478bd9Sstevel@tonic-gate glh->nfree = 0; 1683*7c478bd9Sstevel@tonic-gate glh->nbusy = 0; 1684*7c478bd9Sstevel@tonic-gate glh->nline = 0; 1685*7c478bd9Sstevel@tonic-gate /* 1686*7c478bd9Sstevel@tonic-gate * Change from one finite buffer size to another? 1687*7c478bd9Sstevel@tonic-gate */ 1688*7c478bd9Sstevel@tonic-gate } else { 1689*7c478bd9Sstevel@tonic-gate GlhLineSeg *buffer; /* The resized buffer */ 1690*7c478bd9Sstevel@tonic-gate int nbusy; /* The number of used line segments in the new buffer */ 1691*7c478bd9Sstevel@tonic-gate /* 1692*7c478bd9Sstevel@tonic-gate * Starting from the oldest line in the buffer, discard lines until 1693*7c478bd9Sstevel@tonic-gate * the buffer contains at most 'nbuff' used line segments. 1694*7c478bd9Sstevel@tonic-gate */ 1695*7c478bd9Sstevel@tonic-gate while(glh->list.head && glh->nbusy > nbuff) 1696*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, glh->list.head); 1697*7c478bd9Sstevel@tonic-gate /* 1698*7c478bd9Sstevel@tonic-gate * Attempt to allocate a new buffer. 1699*7c478bd9Sstevel@tonic-gate */ 1700*7c478bd9Sstevel@tonic-gate buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg)); 1701*7c478bd9Sstevel@tonic-gate if(!buffer) { 1702*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 1703*7c478bd9Sstevel@tonic-gate return 1; 1704*7c478bd9Sstevel@tonic-gate }; 1705*7c478bd9Sstevel@tonic-gate /* 1706*7c478bd9Sstevel@tonic-gate * Copy the used segments of the old buffer to the start of the new buffer. 1707*7c478bd9Sstevel@tonic-gate */ 1708*7c478bd9Sstevel@tonic-gate nbusy = 0; 1709*7c478bd9Sstevel@tonic-gate for(i=0; i<GLH_HASH_SIZE; i++) { 1710*7c478bd9Sstevel@tonic-gate GlhHashBucket *b = glh->hash.bucket + i; 1711*7c478bd9Sstevel@tonic-gate GlhHashNode *hnode; 1712*7c478bd9Sstevel@tonic-gate for(hnode=b->lines; hnode; hnode=hnode->next) { 1713*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg = hnode->head; 1714*7c478bd9Sstevel@tonic-gate hnode->head = buffer + nbusy; 1715*7c478bd9Sstevel@tonic-gate for( ; seg; seg=seg->next) { 1716*7c478bd9Sstevel@tonic-gate buffer[nbusy] = *seg; 1717*7c478bd9Sstevel@tonic-gate buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL; 1718*7c478bd9Sstevel@tonic-gate nbusy++; 1719*7c478bd9Sstevel@tonic-gate }; 1720*7c478bd9Sstevel@tonic-gate }; 1721*7c478bd9Sstevel@tonic-gate }; 1722*7c478bd9Sstevel@tonic-gate /* 1723*7c478bd9Sstevel@tonic-gate * Make a list of the new buffer's unused segments. 1724*7c478bd9Sstevel@tonic-gate */ 1725*7c478bd9Sstevel@tonic-gate for(i=nbusy; i<nbuff-1; i++) 1726*7c478bd9Sstevel@tonic-gate buffer[i].next = &buffer[i+1]; 1727*7c478bd9Sstevel@tonic-gate if(i < nbuff) 1728*7c478bd9Sstevel@tonic-gate buffer[i].next = NULL; 1729*7c478bd9Sstevel@tonic-gate /* 1730*7c478bd9Sstevel@tonic-gate * Discard the old buffer. 1731*7c478bd9Sstevel@tonic-gate */ 1732*7c478bd9Sstevel@tonic-gate free(glh->buffer); 1733*7c478bd9Sstevel@tonic-gate /* 1734*7c478bd9Sstevel@tonic-gate * Install the new buffer. 1735*7c478bd9Sstevel@tonic-gate */ 1736*7c478bd9Sstevel@tonic-gate glh->buffer = buffer; 1737*7c478bd9Sstevel@tonic-gate glh->nbuff = nbuff; 1738*7c478bd9Sstevel@tonic-gate glh->nbusy = nbusy; 1739*7c478bd9Sstevel@tonic-gate glh->nfree = nbuff - nbusy; 1740*7c478bd9Sstevel@tonic-gate glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL; 1741*7c478bd9Sstevel@tonic-gate }; 1742*7c478bd9Sstevel@tonic-gate }; 1743*7c478bd9Sstevel@tonic-gate return 0; 1744*7c478bd9Sstevel@tonic-gate } 1745*7c478bd9Sstevel@tonic-gate 1746*7c478bd9Sstevel@tonic-gate /*....................................................................... 1747*7c478bd9Sstevel@tonic-gate * Set an upper limit to the number of lines that can be recorded in the 1748*7c478bd9Sstevel@tonic-gate * history list, or remove a previously specified limit. 1749*7c478bd9Sstevel@tonic-gate * 1750*7c478bd9Sstevel@tonic-gate * Input: 1751*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1752*7c478bd9Sstevel@tonic-gate * max_lines int The maximum number of lines to allow, or -1 to 1753*7c478bd9Sstevel@tonic-gate * cancel a previous limit and allow as many lines 1754*7c478bd9Sstevel@tonic-gate * as will fit in the current history buffer size. 1755*7c478bd9Sstevel@tonic-gate */ 1756*7c478bd9Sstevel@tonic-gate void _glh_limit_history(GlHistory *glh, int max_lines) 1757*7c478bd9Sstevel@tonic-gate { 1758*7c478bd9Sstevel@tonic-gate if(!glh) 1759*7c478bd9Sstevel@tonic-gate return; 1760*7c478bd9Sstevel@tonic-gate /* 1761*7c478bd9Sstevel@tonic-gate * Apply a new limit? 1762*7c478bd9Sstevel@tonic-gate */ 1763*7c478bd9Sstevel@tonic-gate if(max_lines >= 0 && max_lines != glh->max_lines) { 1764*7c478bd9Sstevel@tonic-gate /* 1765*7c478bd9Sstevel@tonic-gate * Count successively older lines until we reach the start of the 1766*7c478bd9Sstevel@tonic-gate * list, or until we have seen max_lines lines (at which point 'node' 1767*7c478bd9Sstevel@tonic-gate * will be line number max_lines+1). 1768*7c478bd9Sstevel@tonic-gate */ 1769*7c478bd9Sstevel@tonic-gate int nline = 0; 1770*7c478bd9Sstevel@tonic-gate GlhLineNode *node; 1771*7c478bd9Sstevel@tonic-gate for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev) 1772*7c478bd9Sstevel@tonic-gate ; 1773*7c478bd9Sstevel@tonic-gate /* 1774*7c478bd9Sstevel@tonic-gate * Discard any lines that exceed the limit. 1775*7c478bd9Sstevel@tonic-gate */ 1776*7c478bd9Sstevel@tonic-gate if(node) { 1777*7c478bd9Sstevel@tonic-gate GlhLineNode *oldest = node->next; /* The oldest line to be kept */ 1778*7c478bd9Sstevel@tonic-gate /* 1779*7c478bd9Sstevel@tonic-gate * Delete nodes from the head of the list until we reach the node that 1780*7c478bd9Sstevel@tonic-gate * is to be kept. 1781*7c478bd9Sstevel@tonic-gate */ 1782*7c478bd9Sstevel@tonic-gate while(glh->list.head && glh->list.head != oldest) 1783*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, glh->list.head); 1784*7c478bd9Sstevel@tonic-gate }; 1785*7c478bd9Sstevel@tonic-gate }; 1786*7c478bd9Sstevel@tonic-gate /* 1787*7c478bd9Sstevel@tonic-gate * Record the new limit. 1788*7c478bd9Sstevel@tonic-gate */ 1789*7c478bd9Sstevel@tonic-gate glh->max_lines = max_lines; 1790*7c478bd9Sstevel@tonic-gate return; 1791*7c478bd9Sstevel@tonic-gate } 1792*7c478bd9Sstevel@tonic-gate 1793*7c478bd9Sstevel@tonic-gate /*....................................................................... 1794*7c478bd9Sstevel@tonic-gate * Discard either all history, or the history associated with the current 1795*7c478bd9Sstevel@tonic-gate * history group. 1796*7c478bd9Sstevel@tonic-gate * 1797*7c478bd9Sstevel@tonic-gate * Input: 1798*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1799*7c478bd9Sstevel@tonic-gate * all_groups int If true, clear all of the history. If false, 1800*7c478bd9Sstevel@tonic-gate * clear only the stored lines associated with the 1801*7c478bd9Sstevel@tonic-gate * currently selected history group. 1802*7c478bd9Sstevel@tonic-gate */ 1803*7c478bd9Sstevel@tonic-gate void _glh_clear_history(GlHistory *glh, int all_groups) 1804*7c478bd9Sstevel@tonic-gate { 1805*7c478bd9Sstevel@tonic-gate int i; 1806*7c478bd9Sstevel@tonic-gate /* 1807*7c478bd9Sstevel@tonic-gate * Check the arguments. 1808*7c478bd9Sstevel@tonic-gate */ 1809*7c478bd9Sstevel@tonic-gate if(!glh) 1810*7c478bd9Sstevel@tonic-gate return; 1811*7c478bd9Sstevel@tonic-gate /* 1812*7c478bd9Sstevel@tonic-gate * Cancel any ongoing search. 1813*7c478bd9Sstevel@tonic-gate */ 1814*7c478bd9Sstevel@tonic-gate (void) _glh_cancel_search(glh); 1815*7c478bd9Sstevel@tonic-gate /* 1816*7c478bd9Sstevel@tonic-gate * Delete all history lines regardless of group? 1817*7c478bd9Sstevel@tonic-gate */ 1818*7c478bd9Sstevel@tonic-gate if(all_groups) { 1819*7c478bd9Sstevel@tonic-gate /* 1820*7c478bd9Sstevel@tonic-gate * Claer the time-ordered list of lines. 1821*7c478bd9Sstevel@tonic-gate */ 1822*7c478bd9Sstevel@tonic-gate _rst_FreeList(glh->list.node_mem); 1823*7c478bd9Sstevel@tonic-gate glh->list.head = glh->list.tail = NULL; 1824*7c478bd9Sstevel@tonic-gate glh->nline = 0; 1825*7c478bd9Sstevel@tonic-gate glh->id_node = NULL; 1826*7c478bd9Sstevel@tonic-gate /* 1827*7c478bd9Sstevel@tonic-gate * Clear the hash table. 1828*7c478bd9Sstevel@tonic-gate */ 1829*7c478bd9Sstevel@tonic-gate for(i=0; i<GLH_HASH_SIZE; i++) 1830*7c478bd9Sstevel@tonic-gate glh->hash.bucket[i].lines = NULL; 1831*7c478bd9Sstevel@tonic-gate _rst_FreeList(glh->hash.node_mem); 1832*7c478bd9Sstevel@tonic-gate /* 1833*7c478bd9Sstevel@tonic-gate * Move all line segment nodes back onto the list of unused segments. 1834*7c478bd9Sstevel@tonic-gate */ 1835*7c478bd9Sstevel@tonic-gate if(glh->buffer) { 1836*7c478bd9Sstevel@tonic-gate glh->unused = glh->buffer; 1837*7c478bd9Sstevel@tonic-gate for(i=0; i<glh->nbuff-1; i++) { 1838*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg = glh->unused + i; 1839*7c478bd9Sstevel@tonic-gate seg->next = seg + 1; 1840*7c478bd9Sstevel@tonic-gate }; 1841*7c478bd9Sstevel@tonic-gate glh->unused[i].next = NULL; 1842*7c478bd9Sstevel@tonic-gate glh->nfree = glh->nbuff; 1843*7c478bd9Sstevel@tonic-gate glh->nbusy = 0; 1844*7c478bd9Sstevel@tonic-gate } else { 1845*7c478bd9Sstevel@tonic-gate glh->unused = NULL; 1846*7c478bd9Sstevel@tonic-gate glh->nbusy = glh->nfree = 0; 1847*7c478bd9Sstevel@tonic-gate }; 1848*7c478bd9Sstevel@tonic-gate /* 1849*7c478bd9Sstevel@tonic-gate * Just delete lines of the current group? 1850*7c478bd9Sstevel@tonic-gate */ 1851*7c478bd9Sstevel@tonic-gate } else { 1852*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The line node being checked */ 1853*7c478bd9Sstevel@tonic-gate GlhLineNode *next; /* The line node that follows 'node' */ 1854*7c478bd9Sstevel@tonic-gate /* 1855*7c478bd9Sstevel@tonic-gate * Search out and delete the line nodes of the current group. 1856*7c478bd9Sstevel@tonic-gate */ 1857*7c478bd9Sstevel@tonic-gate for(node=glh->list.head; node; node=next) { 1858*7c478bd9Sstevel@tonic-gate /* 1859*7c478bd9Sstevel@tonic-gate * Keep a record of the following node before we delete the current 1860*7c478bd9Sstevel@tonic-gate * node. 1861*7c478bd9Sstevel@tonic-gate */ 1862*7c478bd9Sstevel@tonic-gate next = node->next; 1863*7c478bd9Sstevel@tonic-gate /* 1864*7c478bd9Sstevel@tonic-gate * Discard this node? 1865*7c478bd9Sstevel@tonic-gate */ 1866*7c478bd9Sstevel@tonic-gate if(node->group == glh->group) 1867*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, node); 1868*7c478bd9Sstevel@tonic-gate }; 1869*7c478bd9Sstevel@tonic-gate }; 1870*7c478bd9Sstevel@tonic-gate return; 1871*7c478bd9Sstevel@tonic-gate } 1872*7c478bd9Sstevel@tonic-gate 1873*7c478bd9Sstevel@tonic-gate /*....................................................................... 1874*7c478bd9Sstevel@tonic-gate * Temporarily enable or disable the history list. 1875*7c478bd9Sstevel@tonic-gate * 1876*7c478bd9Sstevel@tonic-gate * Input: 1877*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1878*7c478bd9Sstevel@tonic-gate * enable int If true, turn on the history mechanism. If 1879*7c478bd9Sstevel@tonic-gate * false, disable it. 1880*7c478bd9Sstevel@tonic-gate */ 1881*7c478bd9Sstevel@tonic-gate void _glh_toggle_history(GlHistory *glh, int enable) 1882*7c478bd9Sstevel@tonic-gate { 1883*7c478bd9Sstevel@tonic-gate if(glh) 1884*7c478bd9Sstevel@tonic-gate glh->enable = enable; 1885*7c478bd9Sstevel@tonic-gate } 1886*7c478bd9Sstevel@tonic-gate 1887*7c478bd9Sstevel@tonic-gate /*....................................................................... 1888*7c478bd9Sstevel@tonic-gate * Discard a given archived input line. 1889*7c478bd9Sstevel@tonic-gate * 1890*7c478bd9Sstevel@tonic-gate * Input: 1891*7c478bd9Sstevel@tonic-gate * glh GlHistory * The history container object. 1892*7c478bd9Sstevel@tonic-gate * node GlhLineNode * The line to be discarded, specified via its 1893*7c478bd9Sstevel@tonic-gate * entry in the time-ordered list of historical 1894*7c478bd9Sstevel@tonic-gate * input lines. 1895*7c478bd9Sstevel@tonic-gate */ 1896*7c478bd9Sstevel@tonic-gate static void _glh_discard_line(GlHistory *glh, GlhLineNode *node) 1897*7c478bd9Sstevel@tonic-gate { 1898*7c478bd9Sstevel@tonic-gate /* 1899*7c478bd9Sstevel@tonic-gate * Remove the node from the linked list. 1900*7c478bd9Sstevel@tonic-gate */ 1901*7c478bd9Sstevel@tonic-gate if(node->prev) 1902*7c478bd9Sstevel@tonic-gate node->prev->next = node->next; 1903*7c478bd9Sstevel@tonic-gate else 1904*7c478bd9Sstevel@tonic-gate glh->list.head = node->next; 1905*7c478bd9Sstevel@tonic-gate if(node->next) 1906*7c478bd9Sstevel@tonic-gate node->next->prev = node->prev; 1907*7c478bd9Sstevel@tonic-gate else 1908*7c478bd9Sstevel@tonic-gate glh->list.tail = node->prev; 1909*7c478bd9Sstevel@tonic-gate /* 1910*7c478bd9Sstevel@tonic-gate * If we are deleting the node that is marked as the start point of the 1911*7c478bd9Sstevel@tonic-gate * last ID search, remove the cached starting point. 1912*7c478bd9Sstevel@tonic-gate */ 1913*7c478bd9Sstevel@tonic-gate if(node == glh->id_node) 1914*7c478bd9Sstevel@tonic-gate glh->id_node = NULL; 1915*7c478bd9Sstevel@tonic-gate /* 1916*7c478bd9Sstevel@tonic-gate * If we are deleting the node that is marked as the start point of the 1917*7c478bd9Sstevel@tonic-gate * next prefix search, cancel the search. 1918*7c478bd9Sstevel@tonic-gate */ 1919*7c478bd9Sstevel@tonic-gate if(node == glh->recall) 1920*7c478bd9Sstevel@tonic-gate _glh_cancel_search(glh); 1921*7c478bd9Sstevel@tonic-gate /* 1922*7c478bd9Sstevel@tonic-gate * Delete our copy of the line. 1923*7c478bd9Sstevel@tonic-gate */ 1924*7c478bd9Sstevel@tonic-gate node->line = _glh_discard_copy(glh, node->line); 1925*7c478bd9Sstevel@tonic-gate /* 1926*7c478bd9Sstevel@tonic-gate * Return the node to the freelist. 1927*7c478bd9Sstevel@tonic-gate */ 1928*7c478bd9Sstevel@tonic-gate (void) _del_FreeListNode(glh->list.node_mem, node); 1929*7c478bd9Sstevel@tonic-gate /* 1930*7c478bd9Sstevel@tonic-gate * Record the removal of a line from the list. 1931*7c478bd9Sstevel@tonic-gate */ 1932*7c478bd9Sstevel@tonic-gate glh->nline--; 1933*7c478bd9Sstevel@tonic-gate return; 1934*7c478bd9Sstevel@tonic-gate } 1935*7c478bd9Sstevel@tonic-gate 1936*7c478bd9Sstevel@tonic-gate /*....................................................................... 1937*7c478bd9Sstevel@tonic-gate * Lookup the details of a given history line, given its id. 1938*7c478bd9Sstevel@tonic-gate * 1939*7c478bd9Sstevel@tonic-gate * Input: 1940*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 1941*7c478bd9Sstevel@tonic-gate * id GlLineID The sequential number of the line. 1942*7c478bd9Sstevel@tonic-gate * Input/Output: 1943*7c478bd9Sstevel@tonic-gate * line const char ** A pointer to a copy of the history line will be 1944*7c478bd9Sstevel@tonic-gate * assigned to *line. Beware that this pointer may 1945*7c478bd9Sstevel@tonic-gate * be invalidated by the next call to any public 1946*7c478bd9Sstevel@tonic-gate * history function. 1947*7c478bd9Sstevel@tonic-gate * group unsigned * The group membership of the line will be assigned 1948*7c478bd9Sstevel@tonic-gate * to *group. 1949*7c478bd9Sstevel@tonic-gate * timestamp time_t * The timestamp of the line will be assigned to 1950*7c478bd9Sstevel@tonic-gate * *timestamp. 1951*7c478bd9Sstevel@tonic-gate * Output: 1952*7c478bd9Sstevel@tonic-gate * return int 0 - The requested line wasn't found. 1953*7c478bd9Sstevel@tonic-gate * 1 - The line was found. 1954*7c478bd9Sstevel@tonic-gate */ 1955*7c478bd9Sstevel@tonic-gate int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line, 1956*7c478bd9Sstevel@tonic-gate unsigned *group, time_t *timestamp) 1957*7c478bd9Sstevel@tonic-gate { 1958*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The located line location node */ 1959*7c478bd9Sstevel@tonic-gate /* 1960*7c478bd9Sstevel@tonic-gate * Check the arguments. 1961*7c478bd9Sstevel@tonic-gate */ 1962*7c478bd9Sstevel@tonic-gate if(!glh) 1963*7c478bd9Sstevel@tonic-gate return 0; 1964*7c478bd9Sstevel@tonic-gate /* 1965*7c478bd9Sstevel@tonic-gate * Search for the line that has the specified ID. 1966*7c478bd9Sstevel@tonic-gate */ 1967*7c478bd9Sstevel@tonic-gate node = _glh_find_id(glh, id); 1968*7c478bd9Sstevel@tonic-gate /* 1969*7c478bd9Sstevel@tonic-gate * Not found? 1970*7c478bd9Sstevel@tonic-gate */ 1971*7c478bd9Sstevel@tonic-gate if(!node) 1972*7c478bd9Sstevel@tonic-gate return 0; 1973*7c478bd9Sstevel@tonic-gate /* 1974*7c478bd9Sstevel@tonic-gate * Has the history line been requested? 1975*7c478bd9Sstevel@tonic-gate */ 1976*7c478bd9Sstevel@tonic-gate if(line) { 1977*7c478bd9Sstevel@tonic-gate /* 1978*7c478bd9Sstevel@tonic-gate * If necessary, reallocate the lookup buffer to accomodate the size of 1979*7c478bd9Sstevel@tonic-gate * a copy of the located line. 1980*7c478bd9Sstevel@tonic-gate */ 1981*7c478bd9Sstevel@tonic-gate if(node->line->len + 1 > glh->lbuf_dim) { 1982*7c478bd9Sstevel@tonic-gate int lbuf_dim = node->line->len + 1; 1983*7c478bd9Sstevel@tonic-gate char *lbuf = realloc(glh->lbuf, lbuf_dim); 1984*7c478bd9Sstevel@tonic-gate if(!lbuf) { 1985*7c478bd9Sstevel@tonic-gate errno = ENOMEM; 1986*7c478bd9Sstevel@tonic-gate return 0; 1987*7c478bd9Sstevel@tonic-gate }; 1988*7c478bd9Sstevel@tonic-gate glh->lbuf_dim = lbuf_dim; 1989*7c478bd9Sstevel@tonic-gate glh->lbuf = lbuf; 1990*7c478bd9Sstevel@tonic-gate }; 1991*7c478bd9Sstevel@tonic-gate /* 1992*7c478bd9Sstevel@tonic-gate * Copy the history line into the lookup buffer. 1993*7c478bd9Sstevel@tonic-gate */ 1994*7c478bd9Sstevel@tonic-gate _glh_return_line(node->line, glh->lbuf, glh->lbuf_dim); 1995*7c478bd9Sstevel@tonic-gate /* 1996*7c478bd9Sstevel@tonic-gate * Assign the lookup buffer as the returned line pointer. 1997*7c478bd9Sstevel@tonic-gate */ 1998*7c478bd9Sstevel@tonic-gate *line = glh->lbuf; 1999*7c478bd9Sstevel@tonic-gate }; 2000*7c478bd9Sstevel@tonic-gate /* 2001*7c478bd9Sstevel@tonic-gate * Does the caller want to know the group of the line? 2002*7c478bd9Sstevel@tonic-gate */ 2003*7c478bd9Sstevel@tonic-gate if(group) 2004*7c478bd9Sstevel@tonic-gate *group = node->group; 2005*7c478bd9Sstevel@tonic-gate /* 2006*7c478bd9Sstevel@tonic-gate * Does the caller want to know the timestamp of the line? 2007*7c478bd9Sstevel@tonic-gate */ 2008*7c478bd9Sstevel@tonic-gate if(timestamp) 2009*7c478bd9Sstevel@tonic-gate *timestamp = node->timestamp; 2010*7c478bd9Sstevel@tonic-gate return 1; 2011*7c478bd9Sstevel@tonic-gate } 2012*7c478bd9Sstevel@tonic-gate 2013*7c478bd9Sstevel@tonic-gate /*....................................................................... 2014*7c478bd9Sstevel@tonic-gate * Lookup a node in the history list by its ID. 2015*7c478bd9Sstevel@tonic-gate * 2016*7c478bd9Sstevel@tonic-gate * Input: 2017*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2018*7c478bd9Sstevel@tonic-gate * id GlhLineID The ID of the line to be returned. 2019*7c478bd9Sstevel@tonic-gate * Output: 2020*7c478bd9Sstevel@tonic-gate * return GlhLIneNode * The located node, or NULL if not found. 2021*7c478bd9Sstevel@tonic-gate */ 2022*7c478bd9Sstevel@tonic-gate static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id) 2023*7c478bd9Sstevel@tonic-gate { 2024*7c478bd9Sstevel@tonic-gate GlhLineNode *node; /* The node being checked */ 2025*7c478bd9Sstevel@tonic-gate /* 2026*7c478bd9Sstevel@tonic-gate * Is history enabled? 2027*7c478bd9Sstevel@tonic-gate */ 2028*7c478bd9Sstevel@tonic-gate if(!glh->enable || !glh->list.head) 2029*7c478bd9Sstevel@tonic-gate return NULL; 2030*7c478bd9Sstevel@tonic-gate /* 2031*7c478bd9Sstevel@tonic-gate * If possible, start at the end point of the last ID search. 2032*7c478bd9Sstevel@tonic-gate * Otherwise start from the head of the list. 2033*7c478bd9Sstevel@tonic-gate */ 2034*7c478bd9Sstevel@tonic-gate node = glh->id_node; 2035*7c478bd9Sstevel@tonic-gate if(!node) 2036*7c478bd9Sstevel@tonic-gate node = glh->list.head; 2037*7c478bd9Sstevel@tonic-gate /* 2038*7c478bd9Sstevel@tonic-gate * Search forwards from 'node'? 2039*7c478bd9Sstevel@tonic-gate */ 2040*7c478bd9Sstevel@tonic-gate if(node->id < id) { 2041*7c478bd9Sstevel@tonic-gate while(node && node->id != id) 2042*7c478bd9Sstevel@tonic-gate node = node->next; 2043*7c478bd9Sstevel@tonic-gate glh->id_node = node ? node : glh->list.tail; 2044*7c478bd9Sstevel@tonic-gate /* 2045*7c478bd9Sstevel@tonic-gate * Search backwards from 'node'? 2046*7c478bd9Sstevel@tonic-gate */ 2047*7c478bd9Sstevel@tonic-gate } else { 2048*7c478bd9Sstevel@tonic-gate while(node && node->id != id) 2049*7c478bd9Sstevel@tonic-gate node = node->prev; 2050*7c478bd9Sstevel@tonic-gate glh->id_node = node ? node : glh->list.head; 2051*7c478bd9Sstevel@tonic-gate }; 2052*7c478bd9Sstevel@tonic-gate /* 2053*7c478bd9Sstevel@tonic-gate * Return the located node (this will be NULL if the ID wasn't found). 2054*7c478bd9Sstevel@tonic-gate */ 2055*7c478bd9Sstevel@tonic-gate return node; 2056*7c478bd9Sstevel@tonic-gate } 2057*7c478bd9Sstevel@tonic-gate 2058*7c478bd9Sstevel@tonic-gate /*....................................................................... 2059*7c478bd9Sstevel@tonic-gate * Query the state of the history list. Note that any of the input/output 2060*7c478bd9Sstevel@tonic-gate * pointers can be specified as NULL. 2061*7c478bd9Sstevel@tonic-gate * 2062*7c478bd9Sstevel@tonic-gate * Input: 2063*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2064*7c478bd9Sstevel@tonic-gate * Input/Output: 2065*7c478bd9Sstevel@tonic-gate * enabled int * If history is enabled, *enabled will be 2066*7c478bd9Sstevel@tonic-gate * set to 1. Otherwise it will be assigned 0. 2067*7c478bd9Sstevel@tonic-gate * group unsigned * The current history group ID will be assigned 2068*7c478bd9Sstevel@tonic-gate * to *group. 2069*7c478bd9Sstevel@tonic-gate * max_lines int * The currently requested limit on the number 2070*7c478bd9Sstevel@tonic-gate * of history lines in the list, or -1 if 2071*7c478bd9Sstevel@tonic-gate * unlimited. 2072*7c478bd9Sstevel@tonic-gate */ 2073*7c478bd9Sstevel@tonic-gate void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group, 2074*7c478bd9Sstevel@tonic-gate int *max_lines) 2075*7c478bd9Sstevel@tonic-gate { 2076*7c478bd9Sstevel@tonic-gate if(glh) { 2077*7c478bd9Sstevel@tonic-gate if(enabled) 2078*7c478bd9Sstevel@tonic-gate *enabled = glh->enable; 2079*7c478bd9Sstevel@tonic-gate if(group) 2080*7c478bd9Sstevel@tonic-gate *group = glh->group; 2081*7c478bd9Sstevel@tonic-gate if(max_lines) 2082*7c478bd9Sstevel@tonic-gate *max_lines = glh->max_lines; 2083*7c478bd9Sstevel@tonic-gate }; 2084*7c478bd9Sstevel@tonic-gate } 2085*7c478bd9Sstevel@tonic-gate 2086*7c478bd9Sstevel@tonic-gate /*....................................................................... 2087*7c478bd9Sstevel@tonic-gate * Get the range of lines in the history buffer. 2088*7c478bd9Sstevel@tonic-gate * 2089*7c478bd9Sstevel@tonic-gate * Input: 2090*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2091*7c478bd9Sstevel@tonic-gate * Input/Output: 2092*7c478bd9Sstevel@tonic-gate * oldest unsigned long * The sequential entry number of the oldest 2093*7c478bd9Sstevel@tonic-gate * line in the history list will be assigned 2094*7c478bd9Sstevel@tonic-gate * to *oldest, unless there are no lines, in 2095*7c478bd9Sstevel@tonic-gate * which case 0 will be assigned. 2096*7c478bd9Sstevel@tonic-gate * newest unsigned long * The sequential entry number of the newest 2097*7c478bd9Sstevel@tonic-gate * line in the history list will be assigned 2098*7c478bd9Sstevel@tonic-gate * to *newest, unless there are no lines, in 2099*7c478bd9Sstevel@tonic-gate * which case 0 will be assigned. 2100*7c478bd9Sstevel@tonic-gate * nlines int * The number of lines currently in the history 2101*7c478bd9Sstevel@tonic-gate * list. 2102*7c478bd9Sstevel@tonic-gate */ 2103*7c478bd9Sstevel@tonic-gate void _glh_range_of_history(GlHistory *glh, unsigned long *oldest, 2104*7c478bd9Sstevel@tonic-gate unsigned long *newest, int *nlines) 2105*7c478bd9Sstevel@tonic-gate { 2106*7c478bd9Sstevel@tonic-gate if(glh) { 2107*7c478bd9Sstevel@tonic-gate if(oldest) 2108*7c478bd9Sstevel@tonic-gate *oldest = glh->list.head ? glh->list.head->id : 0; 2109*7c478bd9Sstevel@tonic-gate if(newest) 2110*7c478bd9Sstevel@tonic-gate *newest = glh->list.tail ? glh->list.tail->id : 0; 2111*7c478bd9Sstevel@tonic-gate if(nlines) 2112*7c478bd9Sstevel@tonic-gate *nlines = glh->nline; 2113*7c478bd9Sstevel@tonic-gate }; 2114*7c478bd9Sstevel@tonic-gate } 2115*7c478bd9Sstevel@tonic-gate 2116*7c478bd9Sstevel@tonic-gate /*....................................................................... 2117*7c478bd9Sstevel@tonic-gate * Return the size of the history buffer and the amount of the 2118*7c478bd9Sstevel@tonic-gate * buffer that is currently in use. 2119*7c478bd9Sstevel@tonic-gate * 2120*7c478bd9Sstevel@tonic-gate * Input: 2121*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2122*7c478bd9Sstevel@tonic-gate * Input/Output: 2123*7c478bd9Sstevel@tonic-gate * buff_size size_t * The size of the history buffer (bytes). 2124*7c478bd9Sstevel@tonic-gate * buff_used size_t * The amount of the history buffer that 2125*7c478bd9Sstevel@tonic-gate * is currently occupied (bytes). 2126*7c478bd9Sstevel@tonic-gate */ 2127*7c478bd9Sstevel@tonic-gate void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used) 2128*7c478bd9Sstevel@tonic-gate { 2129*7c478bd9Sstevel@tonic-gate if(glh) { 2130*7c478bd9Sstevel@tonic-gate if(buff_size) 2131*7c478bd9Sstevel@tonic-gate *buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE; 2132*7c478bd9Sstevel@tonic-gate /* 2133*7c478bd9Sstevel@tonic-gate * Determine the amount of buffer space that is currently occupied. 2134*7c478bd9Sstevel@tonic-gate */ 2135*7c478bd9Sstevel@tonic-gate if(buff_used) 2136*7c478bd9Sstevel@tonic-gate *buff_used = glh->nbusy * GLH_SEG_SIZE; 2137*7c478bd9Sstevel@tonic-gate }; 2138*7c478bd9Sstevel@tonic-gate } 2139*7c478bd9Sstevel@tonic-gate 2140*7c478bd9Sstevel@tonic-gate /*....................................................................... 2141*7c478bd9Sstevel@tonic-gate * Return extra information (ie. in addition to that provided by errno) 2142*7c478bd9Sstevel@tonic-gate * about the last error to occur in any of the public functions of this 2143*7c478bd9Sstevel@tonic-gate * module. 2144*7c478bd9Sstevel@tonic-gate * 2145*7c478bd9Sstevel@tonic-gate * Input: 2146*7c478bd9Sstevel@tonic-gate * glh GlHistory * The container of the history list. 2147*7c478bd9Sstevel@tonic-gate * Output: 2148*7c478bd9Sstevel@tonic-gate * return const char * A pointer to the internal buffer in which 2149*7c478bd9Sstevel@tonic-gate * the error message is temporarily stored. 2150*7c478bd9Sstevel@tonic-gate */ 2151*7c478bd9Sstevel@tonic-gate const char *_glh_last_error(GlHistory *glh) 2152*7c478bd9Sstevel@tonic-gate { 2153*7c478bd9Sstevel@tonic-gate return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument"; 2154*7c478bd9Sstevel@tonic-gate } 2155*7c478bd9Sstevel@tonic-gate 2156*7c478bd9Sstevel@tonic-gate /*....................................................................... 2157*7c478bd9Sstevel@tonic-gate * Unless already stored, store a copy of the line in the history buffer, 2158*7c478bd9Sstevel@tonic-gate * then return a reference-counted hash-node pointer to this copy. 2159*7c478bd9Sstevel@tonic-gate * 2160*7c478bd9Sstevel@tonic-gate * Input: 2161*7c478bd9Sstevel@tonic-gate * glh GlHistory * The history maintenance buffer. 2162*7c478bd9Sstevel@tonic-gate * line const char * The history line to be recorded. 2163*7c478bd9Sstevel@tonic-gate * n size_t The length of the string, excluding any '\0' 2164*7c478bd9Sstevel@tonic-gate * terminator. 2165*7c478bd9Sstevel@tonic-gate * Output: 2166*7c478bd9Sstevel@tonic-gate * return GlhHashNode * The hash-node containing the stored line, or 2167*7c478bd9Sstevel@tonic-gate * NULL on error. 2168*7c478bd9Sstevel@tonic-gate */ 2169*7c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line, 2170*7c478bd9Sstevel@tonic-gate size_t n) 2171*7c478bd9Sstevel@tonic-gate { 2172*7c478bd9Sstevel@tonic-gate GlhHashBucket *bucket; /* The hash-table bucket of the line */ 2173*7c478bd9Sstevel@tonic-gate GlhHashNode *hnode; /* The hash-table node of the line */ 2174*7c478bd9Sstevel@tonic-gate int i; 2175*7c478bd9Sstevel@tonic-gate /* 2176*7c478bd9Sstevel@tonic-gate * In which bucket should the line be recorded? 2177*7c478bd9Sstevel@tonic-gate */ 2178*7c478bd9Sstevel@tonic-gate bucket = glh_find_bucket(glh, line, n); 2179*7c478bd9Sstevel@tonic-gate /* 2180*7c478bd9Sstevel@tonic-gate * Is the line already recorded there? 2181*7c478bd9Sstevel@tonic-gate */ 2182*7c478bd9Sstevel@tonic-gate hnode = glh_find_hash_node(bucket, line, n); 2183*7c478bd9Sstevel@tonic-gate /* 2184*7c478bd9Sstevel@tonic-gate * If the line isn't recorded in the buffer yet, make room for it. 2185*7c478bd9Sstevel@tonic-gate */ 2186*7c478bd9Sstevel@tonic-gate if(!hnode) { 2187*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* A line segment */ 2188*7c478bd9Sstevel@tonic-gate int offset; /* An offset into line[] */ 2189*7c478bd9Sstevel@tonic-gate /* 2190*7c478bd9Sstevel@tonic-gate * How many string segments will be needed to record the new line, 2191*7c478bd9Sstevel@tonic-gate * including space for a '\0' terminator? 2192*7c478bd9Sstevel@tonic-gate */ 2193*7c478bd9Sstevel@tonic-gate int nseg = ((n+1) + GLH_SEG_SIZE-1) / GLH_SEG_SIZE; 2194*7c478bd9Sstevel@tonic-gate /* 2195*7c478bd9Sstevel@tonic-gate * Discard the oldest history lines in the buffer until at least 2196*7c478bd9Sstevel@tonic-gate * 'nseg' segments have been freed up, or until we run out of buffer 2197*7c478bd9Sstevel@tonic-gate * space. 2198*7c478bd9Sstevel@tonic-gate */ 2199*7c478bd9Sstevel@tonic-gate while(glh->nfree < nseg && glh->nbusy > 0) 2200*7c478bd9Sstevel@tonic-gate _glh_discard_line(glh, glh->list.head); 2201*7c478bd9Sstevel@tonic-gate /* 2202*7c478bd9Sstevel@tonic-gate * If the buffer is smaller than the new line, don't attempt to truncate 2203*7c478bd9Sstevel@tonic-gate * it to fit. Simply don't archive it. 2204*7c478bd9Sstevel@tonic-gate */ 2205*7c478bd9Sstevel@tonic-gate if(glh->nfree < nseg) 2206*7c478bd9Sstevel@tonic-gate return NULL; 2207*7c478bd9Sstevel@tonic-gate /* 2208*7c478bd9Sstevel@tonic-gate * Record the line in the first 'nseg' segments of the list of unused segments. 2209*7c478bd9Sstevel@tonic-gate */ 2210*7c478bd9Sstevel@tonic-gate offset = 0; 2211*7c478bd9Sstevel@tonic-gate for(i=0,seg=glh->unused; i<nseg-1; i++,seg=seg->next, offset+=GLH_SEG_SIZE) 2212*7c478bd9Sstevel@tonic-gate memcpy(seg->s, line + offset, GLH_SEG_SIZE); 2213*7c478bd9Sstevel@tonic-gate memcpy(seg->s, line + offset, n-offset); 2214*7c478bd9Sstevel@tonic-gate seg->s[n-offset] = '\0'; 2215*7c478bd9Sstevel@tonic-gate /* 2216*7c478bd9Sstevel@tonic-gate * Create a new hash-node for the line. 2217*7c478bd9Sstevel@tonic-gate */ 2218*7c478bd9Sstevel@tonic-gate hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem); 2219*7c478bd9Sstevel@tonic-gate if(!hnode) 2220*7c478bd9Sstevel@tonic-gate return NULL; 2221*7c478bd9Sstevel@tonic-gate /* 2222*7c478bd9Sstevel@tonic-gate * Move the copy of the line from the list of unused segments to 2223*7c478bd9Sstevel@tonic-gate * the hash node. 2224*7c478bd9Sstevel@tonic-gate */ 2225*7c478bd9Sstevel@tonic-gate hnode->head = glh->unused; 2226*7c478bd9Sstevel@tonic-gate glh->unused = seg->next; 2227*7c478bd9Sstevel@tonic-gate seg->next = NULL; 2228*7c478bd9Sstevel@tonic-gate glh->nbusy += nseg; 2229*7c478bd9Sstevel@tonic-gate glh->nfree -= nseg; 2230*7c478bd9Sstevel@tonic-gate /* 2231*7c478bd9Sstevel@tonic-gate * Prepend the new hash node to the list within the associated bucket. 2232*7c478bd9Sstevel@tonic-gate */ 2233*7c478bd9Sstevel@tonic-gate hnode->next = bucket->lines; 2234*7c478bd9Sstevel@tonic-gate bucket->lines = hnode; 2235*7c478bd9Sstevel@tonic-gate /* 2236*7c478bd9Sstevel@tonic-gate * Initialize the rest of the members of the hash node. 2237*7c478bd9Sstevel@tonic-gate */ 2238*7c478bd9Sstevel@tonic-gate hnode->len = n; 2239*7c478bd9Sstevel@tonic-gate hnode->reported = 0; 2240*7c478bd9Sstevel@tonic-gate hnode->used = 0; 2241*7c478bd9Sstevel@tonic-gate hnode->bucket = bucket; 2242*7c478bd9Sstevel@tonic-gate }; 2243*7c478bd9Sstevel@tonic-gate /* 2244*7c478bd9Sstevel@tonic-gate * Increment the reference count of the line. 2245*7c478bd9Sstevel@tonic-gate */ 2246*7c478bd9Sstevel@tonic-gate hnode->used++; 2247*7c478bd9Sstevel@tonic-gate return hnode; 2248*7c478bd9Sstevel@tonic-gate } 2249*7c478bd9Sstevel@tonic-gate 2250*7c478bd9Sstevel@tonic-gate /*....................................................................... 2251*7c478bd9Sstevel@tonic-gate * Decrement the reference count of the history line of a given hash-node, 2252*7c478bd9Sstevel@tonic-gate * and if the count reaches zero, delete both the hash-node and the 2253*7c478bd9Sstevel@tonic-gate * buffered copy of the line. 2254*7c478bd9Sstevel@tonic-gate * 2255*7c478bd9Sstevel@tonic-gate * Input: 2256*7c478bd9Sstevel@tonic-gate * glh GlHistory * The history container object. 2257*7c478bd9Sstevel@tonic-gate * hnode GlhHashNode * The node to be removed. 2258*7c478bd9Sstevel@tonic-gate * Output: 2259*7c478bd9Sstevel@tonic-gate * return GlhHashNode * The deleted hash-node (ie. NULL). 2260*7c478bd9Sstevel@tonic-gate */ 2261*7c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode) 2262*7c478bd9Sstevel@tonic-gate { 2263*7c478bd9Sstevel@tonic-gate if(hnode) { 2264*7c478bd9Sstevel@tonic-gate GlhHashBucket *bucket = hnode->bucket; 2265*7c478bd9Sstevel@tonic-gate /* 2266*7c478bd9Sstevel@tonic-gate * If decrementing the reference count of the hash-node doesn't reduce 2267*7c478bd9Sstevel@tonic-gate * the reference count to zero, then the line is still in use in another 2268*7c478bd9Sstevel@tonic-gate * object, so don't delete it yet. Return NULL to indicate that the caller's 2269*7c478bd9Sstevel@tonic-gate * access to the hash-node copy has been deleted. 2270*7c478bd9Sstevel@tonic-gate */ 2271*7c478bd9Sstevel@tonic-gate if(--hnode->used >= 1) 2272*7c478bd9Sstevel@tonic-gate return NULL; 2273*7c478bd9Sstevel@tonic-gate /* 2274*7c478bd9Sstevel@tonic-gate * Remove the hash-node from the list in its parent bucket. 2275*7c478bd9Sstevel@tonic-gate */ 2276*7c478bd9Sstevel@tonic-gate if(bucket->lines == hnode) { 2277*7c478bd9Sstevel@tonic-gate bucket->lines = hnode->next; 2278*7c478bd9Sstevel@tonic-gate } else { 2279*7c478bd9Sstevel@tonic-gate GlhHashNode *prev; /* The node which precedes hnode in the bucket */ 2280*7c478bd9Sstevel@tonic-gate for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next) 2281*7c478bd9Sstevel@tonic-gate ; 2282*7c478bd9Sstevel@tonic-gate if(prev) 2283*7c478bd9Sstevel@tonic-gate prev->next = hnode->next; 2284*7c478bd9Sstevel@tonic-gate }; 2285*7c478bd9Sstevel@tonic-gate hnode->next = NULL; 2286*7c478bd9Sstevel@tonic-gate /* 2287*7c478bd9Sstevel@tonic-gate * Return the line segments of the hash-node to the list of unused segments. 2288*7c478bd9Sstevel@tonic-gate */ 2289*7c478bd9Sstevel@tonic-gate if(hnode->head) { 2290*7c478bd9Sstevel@tonic-gate GlhLineSeg *tail; /* The last node in the list of line segments */ 2291*7c478bd9Sstevel@tonic-gate int nseg; /* The number of segments being discarded */ 2292*7c478bd9Sstevel@tonic-gate /* 2293*7c478bd9Sstevel@tonic-gate * Get the last node of the list of line segments referenced in the hash-node, 2294*7c478bd9Sstevel@tonic-gate * while counting the number of line segments used. 2295*7c478bd9Sstevel@tonic-gate */ 2296*7c478bd9Sstevel@tonic-gate for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next) 2297*7c478bd9Sstevel@tonic-gate ; 2298*7c478bd9Sstevel@tonic-gate /* 2299*7c478bd9Sstevel@tonic-gate * Prepend the list of line segments used by the hash node to the 2300*7c478bd9Sstevel@tonic-gate * list of unused line segments. 2301*7c478bd9Sstevel@tonic-gate */ 2302*7c478bd9Sstevel@tonic-gate tail->next = glh->unused; 2303*7c478bd9Sstevel@tonic-gate glh->unused = hnode->head; 2304*7c478bd9Sstevel@tonic-gate glh->nbusy -= nseg; 2305*7c478bd9Sstevel@tonic-gate glh->nfree += nseg; 2306*7c478bd9Sstevel@tonic-gate }; 2307*7c478bd9Sstevel@tonic-gate /* 2308*7c478bd9Sstevel@tonic-gate * Return the container of the hash-node to the freelist. 2309*7c478bd9Sstevel@tonic-gate */ 2310*7c478bd9Sstevel@tonic-gate hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode); 2311*7c478bd9Sstevel@tonic-gate }; 2312*7c478bd9Sstevel@tonic-gate return NULL; 2313*7c478bd9Sstevel@tonic-gate } 2314*7c478bd9Sstevel@tonic-gate 2315*7c478bd9Sstevel@tonic-gate /*....................................................................... 2316*7c478bd9Sstevel@tonic-gate * Private function to locate the hash bucket associated with a given 2317*7c478bd9Sstevel@tonic-gate * history line. 2318*7c478bd9Sstevel@tonic-gate * 2319*7c478bd9Sstevel@tonic-gate * This uses a hash-function described in the dragon-book 2320*7c478bd9Sstevel@tonic-gate * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and 2321*7c478bd9Sstevel@tonic-gate * Ullman; pub. Adison Wesley) page 435. 2322*7c478bd9Sstevel@tonic-gate * 2323*7c478bd9Sstevel@tonic-gate * Input: 2324*7c478bd9Sstevel@tonic-gate * glh GlHistory * The history container object. 2325*7c478bd9Sstevel@tonic-gate * line const char * The historical line to look up. 2326*7c478bd9Sstevel@tonic-gate * n size_t The length of the line in line[], excluding 2327*7c478bd9Sstevel@tonic-gate * any '\0' terminator. 2328*7c478bd9Sstevel@tonic-gate * Output: 2329*7c478bd9Sstevel@tonic-gate * return GlhHashBucket * The located hash-bucket. 2330*7c478bd9Sstevel@tonic-gate */ 2331*7c478bd9Sstevel@tonic-gate static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line, 2332*7c478bd9Sstevel@tonic-gate size_t n) 2333*7c478bd9Sstevel@tonic-gate { 2334*7c478bd9Sstevel@tonic-gate unsigned long h = 0L; 2335*7c478bd9Sstevel@tonic-gate int i; 2336*7c478bd9Sstevel@tonic-gate for(i=0; i<n; i++) { 2337*7c478bd9Sstevel@tonic-gate unsigned char c = line[i]; 2338*7c478bd9Sstevel@tonic-gate h = 65599UL * h + c; /* 65599 is a prime close to 2^16 */ 2339*7c478bd9Sstevel@tonic-gate }; 2340*7c478bd9Sstevel@tonic-gate return glh->hash.bucket + (h % GLH_HASH_SIZE); 2341*7c478bd9Sstevel@tonic-gate } 2342*7c478bd9Sstevel@tonic-gate 2343*7c478bd9Sstevel@tonic-gate /*....................................................................... 2344*7c478bd9Sstevel@tonic-gate * Find a given history line within a given hash-table bucket. 2345*7c478bd9Sstevel@tonic-gate * 2346*7c478bd9Sstevel@tonic-gate * Input: 2347*7c478bd9Sstevel@tonic-gate * bucket GlhHashBucket * The hash-table bucket in which to search. 2348*7c478bd9Sstevel@tonic-gate * line const char * The historical line to lookup. 2349*7c478bd9Sstevel@tonic-gate * n size_t The length of the line in line[], excluding 2350*7c478bd9Sstevel@tonic-gate * any '\0' terminator. 2351*7c478bd9Sstevel@tonic-gate * Output: 2352*7c478bd9Sstevel@tonic-gate * return GlhHashNode * The hash-table entry of the line, or NULL 2353*7c478bd9Sstevel@tonic-gate * if not found. 2354*7c478bd9Sstevel@tonic-gate */ 2355*7c478bd9Sstevel@tonic-gate static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line, 2356*7c478bd9Sstevel@tonic-gate size_t n) 2357*7c478bd9Sstevel@tonic-gate { 2358*7c478bd9Sstevel@tonic-gate GlhHashNode *node; /* A node in the list of lines in the bucket */ 2359*7c478bd9Sstevel@tonic-gate /* 2360*7c478bd9Sstevel@tonic-gate * Compare each of the lines in the list of lines, against 'line'. 2361*7c478bd9Sstevel@tonic-gate */ 2362*7c478bd9Sstevel@tonic-gate for(node=bucket->lines; node; node=node->next) { 2363*7c478bd9Sstevel@tonic-gate if(_glh_is_line(node, line, n)) 2364*7c478bd9Sstevel@tonic-gate return node; 2365*7c478bd9Sstevel@tonic-gate }; 2366*7c478bd9Sstevel@tonic-gate return NULL; 2367*7c478bd9Sstevel@tonic-gate } 2368*7c478bd9Sstevel@tonic-gate 2369*7c478bd9Sstevel@tonic-gate /*....................................................................... 2370*7c478bd9Sstevel@tonic-gate * Return non-zero if a given string is equal to a given segmented line 2371*7c478bd9Sstevel@tonic-gate * node. 2372*7c478bd9Sstevel@tonic-gate * 2373*7c478bd9Sstevel@tonic-gate * Input: 2374*7c478bd9Sstevel@tonic-gate * hash GlhHashNode * The hash-table entry of the line. 2375*7c478bd9Sstevel@tonic-gate * line const char * The string to be compared to the segmented 2376*7c478bd9Sstevel@tonic-gate * line. 2377*7c478bd9Sstevel@tonic-gate * n size_t The length of the line in line[], excluding 2378*7c478bd9Sstevel@tonic-gate * any '\0' terminator. 2379*7c478bd9Sstevel@tonic-gate * Output: 2380*7c478bd9Sstevel@tonic-gate * return int 0 - The lines differ. 2381*7c478bd9Sstevel@tonic-gate * 1 - The lines are the same. 2382*7c478bd9Sstevel@tonic-gate */ 2383*7c478bd9Sstevel@tonic-gate static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n) 2384*7c478bd9Sstevel@tonic-gate { 2385*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* A node in the list of line segments */ 2386*7c478bd9Sstevel@tonic-gate int i; 2387*7c478bd9Sstevel@tonic-gate /* 2388*7c478bd9Sstevel@tonic-gate * Do the two lines have the same length? 2389*7c478bd9Sstevel@tonic-gate */ 2390*7c478bd9Sstevel@tonic-gate if(n != hash->len) 2391*7c478bd9Sstevel@tonic-gate return 0; 2392*7c478bd9Sstevel@tonic-gate /* 2393*7c478bd9Sstevel@tonic-gate * Compare the characters of the segmented and unsegmented versions 2394*7c478bd9Sstevel@tonic-gate * of the line. 2395*7c478bd9Sstevel@tonic-gate */ 2396*7c478bd9Sstevel@tonic-gate for(seg=hash->head; n>0 && seg; seg=seg->next) { 2397*7c478bd9Sstevel@tonic-gate const char *s = seg->s; 2398*7c478bd9Sstevel@tonic-gate for(i=0; n>0 && i<GLH_SEG_SIZE; i++,n--) { 2399*7c478bd9Sstevel@tonic-gate if(*line++ != *s++) 2400*7c478bd9Sstevel@tonic-gate return 0; 2401*7c478bd9Sstevel@tonic-gate }; 2402*7c478bd9Sstevel@tonic-gate }; 2403*7c478bd9Sstevel@tonic-gate return 1; 2404*7c478bd9Sstevel@tonic-gate } 2405*7c478bd9Sstevel@tonic-gate 2406*7c478bd9Sstevel@tonic-gate /*....................................................................... 2407*7c478bd9Sstevel@tonic-gate * Return non-zero if a given line has the specified segmented search 2408*7c478bd9Sstevel@tonic-gate * prefix. 2409*7c478bd9Sstevel@tonic-gate * 2410*7c478bd9Sstevel@tonic-gate * Input: 2411*7c478bd9Sstevel@tonic-gate * line GlhHashNode * The line to be compared against the prefix. 2412*7c478bd9Sstevel@tonic-gate * prefix GlhHashNode * The search prefix, or NULL to match any string. 2413*7c478bd9Sstevel@tonic-gate * Output: 2414*7c478bd9Sstevel@tonic-gate * return int 0 - The line doesn't have the specified prefix. 2415*7c478bd9Sstevel@tonic-gate * 1 - The line has the specified prefix. 2416*7c478bd9Sstevel@tonic-gate */ 2417*7c478bd9Sstevel@tonic-gate static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix) 2418*7c478bd9Sstevel@tonic-gate { 2419*7c478bd9Sstevel@tonic-gate GlhLineStream lstr; /* The stream that is used to traverse 'line' */ 2420*7c478bd9Sstevel@tonic-gate GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ 2421*7c478bd9Sstevel@tonic-gate /* 2422*7c478bd9Sstevel@tonic-gate * When prefix==NULL, this means that the nul string 2423*7c478bd9Sstevel@tonic-gate * is to be matched, and this matches all lines. 2424*7c478bd9Sstevel@tonic-gate */ 2425*7c478bd9Sstevel@tonic-gate if(!prefix) 2426*7c478bd9Sstevel@tonic-gate return 1; 2427*7c478bd9Sstevel@tonic-gate /* 2428*7c478bd9Sstevel@tonic-gate * Wrap the two history lines that are to be compared in iterator 2429*7c478bd9Sstevel@tonic-gate * stream objects. 2430*7c478bd9Sstevel@tonic-gate */ 2431*7c478bd9Sstevel@tonic-gate glh_init_stream(&lstr, line); 2432*7c478bd9Sstevel@tonic-gate glh_init_stream(&pstr, prefix); 2433*7c478bd9Sstevel@tonic-gate /* 2434*7c478bd9Sstevel@tonic-gate * If the prefix contains a glob pattern, match the prefix as a glob 2435*7c478bd9Sstevel@tonic-gate * pattern. 2436*7c478bd9Sstevel@tonic-gate */ 2437*7c478bd9Sstevel@tonic-gate if(glh_contains_glob(prefix)) 2438*7c478bd9Sstevel@tonic-gate return glh_line_matches_glob(&lstr, &pstr); 2439*7c478bd9Sstevel@tonic-gate /* 2440*7c478bd9Sstevel@tonic-gate * Is the prefix longer than the line being compared against it? 2441*7c478bd9Sstevel@tonic-gate */ 2442*7c478bd9Sstevel@tonic-gate if(prefix->len > line->len) 2443*7c478bd9Sstevel@tonic-gate return 0; 2444*7c478bd9Sstevel@tonic-gate /* 2445*7c478bd9Sstevel@tonic-gate * Compare the line to the prefix. 2446*7c478bd9Sstevel@tonic-gate */ 2447*7c478bd9Sstevel@tonic-gate while(pstr.c != '\0' && pstr.c == lstr.c) { 2448*7c478bd9Sstevel@tonic-gate glh_step_stream(&lstr); 2449*7c478bd9Sstevel@tonic-gate glh_step_stream(&pstr); 2450*7c478bd9Sstevel@tonic-gate }; 2451*7c478bd9Sstevel@tonic-gate /* 2452*7c478bd9Sstevel@tonic-gate * Did we reach the end of the prefix string before finding 2453*7c478bd9Sstevel@tonic-gate * any differences? 2454*7c478bd9Sstevel@tonic-gate */ 2455*7c478bd9Sstevel@tonic-gate return pstr.c == '\0'; 2456*7c478bd9Sstevel@tonic-gate } 2457*7c478bd9Sstevel@tonic-gate 2458*7c478bd9Sstevel@tonic-gate /*....................................................................... 2459*7c478bd9Sstevel@tonic-gate * Copy a given history line into a specified output string. 2460*7c478bd9Sstevel@tonic-gate * 2461*7c478bd9Sstevel@tonic-gate * Input: 2462*7c478bd9Sstevel@tonic-gate * hash GlhHashNode The hash-table entry of the history line to 2463*7c478bd9Sstevel@tonic-gate * be copied. 2464*7c478bd9Sstevel@tonic-gate * line char * A copy of the history line. 2465*7c478bd9Sstevel@tonic-gate * dim size_t The allocated dimension of the line buffer. 2466*7c478bd9Sstevel@tonic-gate */ 2467*7c478bd9Sstevel@tonic-gate static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim) 2468*7c478bd9Sstevel@tonic-gate { 2469*7c478bd9Sstevel@tonic-gate GlhLineSeg *seg; /* A node in the list of line segments */ 2470*7c478bd9Sstevel@tonic-gate int i; 2471*7c478bd9Sstevel@tonic-gate for(seg=hash->head; dim>0 && seg; seg=seg->next) { 2472*7c478bd9Sstevel@tonic-gate const char *s = seg->s; 2473*7c478bd9Sstevel@tonic-gate for(i=0; dim>0 && i<GLH_SEG_SIZE; i++,dim--) 2474*7c478bd9Sstevel@tonic-gate *line++ = *s++; 2475*7c478bd9Sstevel@tonic-gate }; 2476*7c478bd9Sstevel@tonic-gate /* 2477*7c478bd9Sstevel@tonic-gate * If the line wouldn't fit in the output buffer, replace the last character 2478*7c478bd9Sstevel@tonic-gate * with a '\0' terminator. 2479*7c478bd9Sstevel@tonic-gate */ 2480*7c478bd9Sstevel@tonic-gate if(dim==0) 2481*7c478bd9Sstevel@tonic-gate line[-1] = '\0'; 2482*7c478bd9Sstevel@tonic-gate } 2483*7c478bd9Sstevel@tonic-gate 2484*7c478bd9Sstevel@tonic-gate /*....................................................................... 2485*7c478bd9Sstevel@tonic-gate * This function should be called whenever a new line recall is 2486*7c478bd9Sstevel@tonic-gate * attempted. It preserves a copy of the current input line in the 2487*7c478bd9Sstevel@tonic-gate * history list while other lines in the history list are being 2488*7c478bd9Sstevel@tonic-gate * returned. 2489*7c478bd9Sstevel@tonic-gate * 2490*7c478bd9Sstevel@tonic-gate * Input: 2491*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2492*7c478bd9Sstevel@tonic-gate * line char * The current contents of the input line buffer. 2493*7c478bd9Sstevel@tonic-gate * Output: 2494*7c478bd9Sstevel@tonic-gate * return int 0 - OK. 2495*7c478bd9Sstevel@tonic-gate * 1 - Error. 2496*7c478bd9Sstevel@tonic-gate */ 2497*7c478bd9Sstevel@tonic-gate static int _glh_prepare_for_recall(GlHistory *glh, char *line) 2498*7c478bd9Sstevel@tonic-gate { 2499*7c478bd9Sstevel@tonic-gate /* 2500*7c478bd9Sstevel@tonic-gate * If a recall session has already been started, but we have returned 2501*7c478bd9Sstevel@tonic-gate * to the preserved copy of the input line, if the user has changed 2502*7c478bd9Sstevel@tonic-gate * this line, we should replace the preserved copy of the original 2503*7c478bd9Sstevel@tonic-gate * input line with the new one. To do this simply cancel the session, 2504*7c478bd9Sstevel@tonic-gate * so that a new session is started below. 2505*7c478bd9Sstevel@tonic-gate */ 2506*7c478bd9Sstevel@tonic-gate if(glh->recall && glh->recall == glh->list.tail && 2507*7c478bd9Sstevel@tonic-gate !_glh_is_line(glh->recall->line, line, strlen(line))) { 2508*7c478bd9Sstevel@tonic-gate _glh_cancel_search(glh); 2509*7c478bd9Sstevel@tonic-gate }; 2510*7c478bd9Sstevel@tonic-gate /* 2511*7c478bd9Sstevel@tonic-gate * If this is the first line recall of a new recall session, save the 2512*7c478bd9Sstevel@tonic-gate * current line for potential recall later, and mark it as the last 2513*7c478bd9Sstevel@tonic-gate * line recalled. 2514*7c478bd9Sstevel@tonic-gate */ 2515*7c478bd9Sstevel@tonic-gate if(!glh->recall) { 2516*7c478bd9Sstevel@tonic-gate if(_glh_add_history(glh, line, 1)) 2517*7c478bd9Sstevel@tonic-gate return 1; 2518*7c478bd9Sstevel@tonic-gate glh->recall = glh->list.tail; 2519*7c478bd9Sstevel@tonic-gate /* 2520*7c478bd9Sstevel@tonic-gate * The above call to _glh_add_history() will have incremented the line 2521*7c478bd9Sstevel@tonic-gate * sequence number, after adding the line. Since we only want this to 2522*7c478bd9Sstevel@tonic-gate * to be incremented for permanently entered lines, decrement it again. 2523*7c478bd9Sstevel@tonic-gate */ 2524*7c478bd9Sstevel@tonic-gate glh->seq--; 2525*7c478bd9Sstevel@tonic-gate }; 2526*7c478bd9Sstevel@tonic-gate return 0; 2527*7c478bd9Sstevel@tonic-gate } 2528*7c478bd9Sstevel@tonic-gate 2529*7c478bd9Sstevel@tonic-gate /*....................................................................... 2530*7c478bd9Sstevel@tonic-gate * Return non-zero if a history search session is currently in progress. 2531*7c478bd9Sstevel@tonic-gate * 2532*7c478bd9Sstevel@tonic-gate * Input: 2533*7c478bd9Sstevel@tonic-gate * glh GlHistory * The input-line history maintenance object. 2534*7c478bd9Sstevel@tonic-gate * Output: 2535*7c478bd9Sstevel@tonic-gate * return int 0 - No search is currently in progress. 2536*7c478bd9Sstevel@tonic-gate * 1 - A search is in progress. 2537*7c478bd9Sstevel@tonic-gate */ 2538*7c478bd9Sstevel@tonic-gate int _glh_search_active(GlHistory *glh) 2539*7c478bd9Sstevel@tonic-gate { 2540*7c478bd9Sstevel@tonic-gate return glh && glh->recall; 2541*7c478bd9Sstevel@tonic-gate } 2542*7c478bd9Sstevel@tonic-gate 2543*7c478bd9Sstevel@tonic-gate /*....................................................................... 2544*7c478bd9Sstevel@tonic-gate * Initialize a character iterator object to point to the start of a 2545*7c478bd9Sstevel@tonic-gate * given history line. The first character of the line will be placed 2546*7c478bd9Sstevel@tonic-gate * in str->c, and subsequent characters can be placed there by calling 2547*7c478bd9Sstevel@tonic-gate * glh_strep_stream(). 2548*7c478bd9Sstevel@tonic-gate * 2549*7c478bd9Sstevel@tonic-gate * Input: 2550*7c478bd9Sstevel@tonic-gate * str GlhLineStream * The iterator object to be initialized. 2551*7c478bd9Sstevel@tonic-gate * line GlhHashNode * The history line to be iterated over (a 2552*7c478bd9Sstevel@tonic-gate * NULL value here, is interpretted as an 2553*7c478bd9Sstevel@tonic-gate * empty string by glh_step_stream()). 2554*7c478bd9Sstevel@tonic-gate */ 2555*7c478bd9Sstevel@tonic-gate static void glh_init_stream(GlhLineStream *str, GlhHashNode *line) 2556*7c478bd9Sstevel@tonic-gate { 2557*7c478bd9Sstevel@tonic-gate str->seg = line ? line->head : NULL; 2558*7c478bd9Sstevel@tonic-gate str->posn = 0; 2559*7c478bd9Sstevel@tonic-gate str->c = str->seg ? str->seg->s[0] : '\0'; 2560*7c478bd9Sstevel@tonic-gate } 2561*7c478bd9Sstevel@tonic-gate 2562*7c478bd9Sstevel@tonic-gate /*....................................................................... 2563*7c478bd9Sstevel@tonic-gate * Copy the next unread character in the line being iterated, in str->c. 2564*7c478bd9Sstevel@tonic-gate * Once the end of the history line has been reached, all futher calls 2565*7c478bd9Sstevel@tonic-gate * set str->c to '\0'. 2566*7c478bd9Sstevel@tonic-gate * 2567*7c478bd9Sstevel@tonic-gate * Input: 2568*7c478bd9Sstevel@tonic-gate * str GlhLineStream * The history-line iterator to read from. 2569*7c478bd9Sstevel@tonic-gate */ 2570*7c478bd9Sstevel@tonic-gate static void glh_step_stream(GlhLineStream *str) 2571*7c478bd9Sstevel@tonic-gate { 2572*7c478bd9Sstevel@tonic-gate /* 2573*7c478bd9Sstevel@tonic-gate * Get the character from the current iterator position within the line. 2574*7c478bd9Sstevel@tonic-gate */ 2575*7c478bd9Sstevel@tonic-gate str->c = str->seg ? str->seg->s[str->posn] : '\0'; 2576*7c478bd9Sstevel@tonic-gate /* 2577*7c478bd9Sstevel@tonic-gate * Unless we have reached the end of the string, move the iterator 2578*7c478bd9Sstevel@tonic-gate * to the position of the next character in the line. 2579*7c478bd9Sstevel@tonic-gate */ 2580*7c478bd9Sstevel@tonic-gate if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) { 2581*7c478bd9Sstevel@tonic-gate str->posn = 0; 2582*7c478bd9Sstevel@tonic-gate str->seg = str->seg->next; 2583*7c478bd9Sstevel@tonic-gate }; 2584*7c478bd9Sstevel@tonic-gate } 2585*7c478bd9Sstevel@tonic-gate 2586*7c478bd9Sstevel@tonic-gate /*....................................................................... 2587*7c478bd9Sstevel@tonic-gate * Return non-zero if the specified search prefix contains any glob 2588*7c478bd9Sstevel@tonic-gate * wildcard characters. 2589*7c478bd9Sstevel@tonic-gate * 2590*7c478bd9Sstevel@tonic-gate * Input: 2591*7c478bd9Sstevel@tonic-gate * prefix GlhHashNode * The search prefix. 2592*7c478bd9Sstevel@tonic-gate * Output: 2593*7c478bd9Sstevel@tonic-gate * return int 0 - The prefix doesn't contain any globbing 2594*7c478bd9Sstevel@tonic-gate * characters. 2595*7c478bd9Sstevel@tonic-gate * 1 - The prefix contains at least one 2596*7c478bd9Sstevel@tonic-gate * globbing character. 2597*7c478bd9Sstevel@tonic-gate */ 2598*7c478bd9Sstevel@tonic-gate static int glh_contains_glob(GlhHashNode *prefix) 2599*7c478bd9Sstevel@tonic-gate { 2600*7c478bd9Sstevel@tonic-gate GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */ 2601*7c478bd9Sstevel@tonic-gate /* 2602*7c478bd9Sstevel@tonic-gate * Wrap a stream iterator around the prefix, so that we can traverse it 2603*7c478bd9Sstevel@tonic-gate * without worrying about line-segmentation. 2604*7c478bd9Sstevel@tonic-gate */ 2605*7c478bd9Sstevel@tonic-gate glh_init_stream(&pstr, prefix); 2606*7c478bd9Sstevel@tonic-gate /* 2607*7c478bd9Sstevel@tonic-gate * Search for unescaped wildcard characters. 2608*7c478bd9Sstevel@tonic-gate */ 2609*7c478bd9Sstevel@tonic-gate while(pstr.c != '\0') { 2610*7c478bd9Sstevel@tonic-gate switch(pstr.c) { 2611*7c478bd9Sstevel@tonic-gate case '\\': /* Skip escaped characters */ 2612*7c478bd9Sstevel@tonic-gate glh_step_stream(&pstr); 2613*7c478bd9Sstevel@tonic-gate break; 2614*7c478bd9Sstevel@tonic-gate case '*': case '?': case '[': /* A wildcard character? */ 2615*7c478bd9Sstevel@tonic-gate return 1; 2616*7c478bd9Sstevel@tonic-gate break; 2617*7c478bd9Sstevel@tonic-gate }; 2618*7c478bd9Sstevel@tonic-gate glh_step_stream(&pstr); 2619*7c478bd9Sstevel@tonic-gate }; 2620*7c478bd9Sstevel@tonic-gate /* 2621*7c478bd9Sstevel@tonic-gate * No wildcard characters were found. 2622*7c478bd9Sstevel@tonic-gate */ 2623*7c478bd9Sstevel@tonic-gate return 0; 2624*7c478bd9Sstevel@tonic-gate } 2625*7c478bd9Sstevel@tonic-gate 2626*7c478bd9Sstevel@tonic-gate /*....................................................................... 2627*7c478bd9Sstevel@tonic-gate * Return non-zero if the history line matches a search prefix containing 2628*7c478bd9Sstevel@tonic-gate * a glob pattern. 2629*7c478bd9Sstevel@tonic-gate * 2630*7c478bd9Sstevel@tonic-gate * Input: 2631*7c478bd9Sstevel@tonic-gate * lstr GlhLineStream * The iterator stream being used to traverse 2632*7c478bd9Sstevel@tonic-gate * the history line that is being matched. 2633*7c478bd9Sstevel@tonic-gate * pstr GlhLineStream * The iterator stream being used to traverse 2634*7c478bd9Sstevel@tonic-gate * the pattern. 2635*7c478bd9Sstevel@tonic-gate * Output: 2636*7c478bd9Sstevel@tonic-gate * return int 0 - Doesn't match. 2637*7c478bd9Sstevel@tonic-gate * 1 - The line matches the pattern. 2638*7c478bd9Sstevel@tonic-gate */ 2639*7c478bd9Sstevel@tonic-gate static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr) 2640*7c478bd9Sstevel@tonic-gate { 2641*7c478bd9Sstevel@tonic-gate /* 2642*7c478bd9Sstevel@tonic-gate * Match each character of the pattern until we reach the end of the 2643*7c478bd9Sstevel@tonic-gate * pattern. 2644*7c478bd9Sstevel@tonic-gate */ 2645*7c478bd9Sstevel@tonic-gate while(pstr->c != '\0') { 2646*7c478bd9Sstevel@tonic-gate /* 2647*7c478bd9Sstevel@tonic-gate * Handle the next character of the pattern. 2648*7c478bd9Sstevel@tonic-gate */ 2649*7c478bd9Sstevel@tonic-gate switch(pstr->c) { 2650*7c478bd9Sstevel@tonic-gate /* 2651*7c478bd9Sstevel@tonic-gate * A match zero-or-more characters wildcard operator. 2652*7c478bd9Sstevel@tonic-gate */ 2653*7c478bd9Sstevel@tonic-gate case '*': 2654*7c478bd9Sstevel@tonic-gate /* 2655*7c478bd9Sstevel@tonic-gate * Skip the '*' character in the pattern. 2656*7c478bd9Sstevel@tonic-gate */ 2657*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2658*7c478bd9Sstevel@tonic-gate /* 2659*7c478bd9Sstevel@tonic-gate * If the pattern ends with the '*' wildcard, then the 2660*7c478bd9Sstevel@tonic-gate * rest of the line matches this. 2661*7c478bd9Sstevel@tonic-gate */ 2662*7c478bd9Sstevel@tonic-gate if(pstr->c == '\0') 2663*7c478bd9Sstevel@tonic-gate return 1; 2664*7c478bd9Sstevel@tonic-gate /* 2665*7c478bd9Sstevel@tonic-gate * Using the wildcard to match successively longer sections of 2666*7c478bd9Sstevel@tonic-gate * the remaining characters of the line, attempt to match 2667*7c478bd9Sstevel@tonic-gate * the tail of the line against the tail of the pattern. 2668*7c478bd9Sstevel@tonic-gate */ 2669*7c478bd9Sstevel@tonic-gate while(lstr->c) { 2670*7c478bd9Sstevel@tonic-gate GlhLineStream old_lstr = *lstr; 2671*7c478bd9Sstevel@tonic-gate GlhLineStream old_pstr = *pstr; 2672*7c478bd9Sstevel@tonic-gate if(glh_line_matches_glob(lstr, pstr)) 2673*7c478bd9Sstevel@tonic-gate return 1; 2674*7c478bd9Sstevel@tonic-gate /* 2675*7c478bd9Sstevel@tonic-gate * Restore the line and pattern iterators for a new try. 2676*7c478bd9Sstevel@tonic-gate */ 2677*7c478bd9Sstevel@tonic-gate *lstr = old_lstr; 2678*7c478bd9Sstevel@tonic-gate *pstr = old_pstr; 2679*7c478bd9Sstevel@tonic-gate /* 2680*7c478bd9Sstevel@tonic-gate * Prepare to try again, one character further into the line. 2681*7c478bd9Sstevel@tonic-gate */ 2682*7c478bd9Sstevel@tonic-gate glh_step_stream(lstr); 2683*7c478bd9Sstevel@tonic-gate }; 2684*7c478bd9Sstevel@tonic-gate return 0; /* The pattern following the '*' didn't match */ 2685*7c478bd9Sstevel@tonic-gate break; 2686*7c478bd9Sstevel@tonic-gate /* 2687*7c478bd9Sstevel@tonic-gate * A match-one-character wildcard operator. 2688*7c478bd9Sstevel@tonic-gate */ 2689*7c478bd9Sstevel@tonic-gate case '?': 2690*7c478bd9Sstevel@tonic-gate /* 2691*7c478bd9Sstevel@tonic-gate * If there is a character to be matched, skip it and advance the 2692*7c478bd9Sstevel@tonic-gate * pattern pointer. 2693*7c478bd9Sstevel@tonic-gate */ 2694*7c478bd9Sstevel@tonic-gate if(lstr->c) { 2695*7c478bd9Sstevel@tonic-gate glh_step_stream(lstr); 2696*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2697*7c478bd9Sstevel@tonic-gate /* 2698*7c478bd9Sstevel@tonic-gate * If we hit the end of the line, there is no character 2699*7c478bd9Sstevel@tonic-gate * matching the operator, so the pattern doesn't match. 2700*7c478bd9Sstevel@tonic-gate */ 2701*7c478bd9Sstevel@tonic-gate } else { 2702*7c478bd9Sstevel@tonic-gate return 0; 2703*7c478bd9Sstevel@tonic-gate }; 2704*7c478bd9Sstevel@tonic-gate break; 2705*7c478bd9Sstevel@tonic-gate /* 2706*7c478bd9Sstevel@tonic-gate * A character range operator, with the character ranges enclosed 2707*7c478bd9Sstevel@tonic-gate * in matching square brackets. 2708*7c478bd9Sstevel@tonic-gate */ 2709*7c478bd9Sstevel@tonic-gate case '[': 2710*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); /* Skip the '[' character */ 2711*7c478bd9Sstevel@tonic-gate if(!lstr->c || !glh_matches_range(lstr->c, pstr)) 2712*7c478bd9Sstevel@tonic-gate return 0; 2713*7c478bd9Sstevel@tonic-gate glh_step_stream(lstr); /* Skip the character that matched */ 2714*7c478bd9Sstevel@tonic-gate break; 2715*7c478bd9Sstevel@tonic-gate /* 2716*7c478bd9Sstevel@tonic-gate * A backslash in the pattern prevents the following character as 2717*7c478bd9Sstevel@tonic-gate * being seen as a special character. 2718*7c478bd9Sstevel@tonic-gate */ 2719*7c478bd9Sstevel@tonic-gate case '\\': 2720*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); /* Skip the backslash */ 2721*7c478bd9Sstevel@tonic-gate /* Note fallthrough to default */ 2722*7c478bd9Sstevel@tonic-gate /* 2723*7c478bd9Sstevel@tonic-gate * A normal character to be matched explicitly. 2724*7c478bd9Sstevel@tonic-gate */ 2725*7c478bd9Sstevel@tonic-gate default: 2726*7c478bd9Sstevel@tonic-gate if(lstr->c == pstr->c) { 2727*7c478bd9Sstevel@tonic-gate glh_step_stream(lstr); 2728*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2729*7c478bd9Sstevel@tonic-gate } else { 2730*7c478bd9Sstevel@tonic-gate return 0; 2731*7c478bd9Sstevel@tonic-gate }; 2732*7c478bd9Sstevel@tonic-gate break; 2733*7c478bd9Sstevel@tonic-gate }; 2734*7c478bd9Sstevel@tonic-gate }; 2735*7c478bd9Sstevel@tonic-gate /* 2736*7c478bd9Sstevel@tonic-gate * To get here, pattern must have been exhausted. The line only 2737*7c478bd9Sstevel@tonic-gate * matches the pattern if the line as also been exhausted. 2738*7c478bd9Sstevel@tonic-gate */ 2739*7c478bd9Sstevel@tonic-gate return pstr->c == '\0' && lstr->c == '\0'; 2740*7c478bd9Sstevel@tonic-gate } 2741*7c478bd9Sstevel@tonic-gate 2742*7c478bd9Sstevel@tonic-gate /*....................................................................... 2743*7c478bd9Sstevel@tonic-gate * Match a character range expression terminated by an unescaped close 2744*7c478bd9Sstevel@tonic-gate * square bracket. 2745*7c478bd9Sstevel@tonic-gate * 2746*7c478bd9Sstevel@tonic-gate * Input: 2747*7c478bd9Sstevel@tonic-gate * c char The character to be matched with the range 2748*7c478bd9Sstevel@tonic-gate * pattern. 2749*7c478bd9Sstevel@tonic-gate * pstr GlhLineStream * The iterator stream being used to traverse 2750*7c478bd9Sstevel@tonic-gate * the pattern. 2751*7c478bd9Sstevel@tonic-gate * Output: 2752*7c478bd9Sstevel@tonic-gate * return int 0 - Doesn't match. 2753*7c478bd9Sstevel@tonic-gate * 1 - The character matched. 2754*7c478bd9Sstevel@tonic-gate */ 2755*7c478bd9Sstevel@tonic-gate static int glh_matches_range(char c, GlhLineStream *pstr) 2756*7c478bd9Sstevel@tonic-gate { 2757*7c478bd9Sstevel@tonic-gate int invert = 0; /* True to invert the sense of the match */ 2758*7c478bd9Sstevel@tonic-gate int matched = 0; /* True if the character matched the pattern */ 2759*7c478bd9Sstevel@tonic-gate char lastc = '\0'; /* The previous character in the pattern */ 2760*7c478bd9Sstevel@tonic-gate /* 2761*7c478bd9Sstevel@tonic-gate * If the first character is a caret, the sense of the match is 2762*7c478bd9Sstevel@tonic-gate * inverted and only if the character isn't one of those in the 2763*7c478bd9Sstevel@tonic-gate * range, do we say that it matches. 2764*7c478bd9Sstevel@tonic-gate */ 2765*7c478bd9Sstevel@tonic-gate if(pstr->c == '^') { 2766*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2767*7c478bd9Sstevel@tonic-gate invert = 1; 2768*7c478bd9Sstevel@tonic-gate }; 2769*7c478bd9Sstevel@tonic-gate /* 2770*7c478bd9Sstevel@tonic-gate * The hyphen is only a special character when it follows the first 2771*7c478bd9Sstevel@tonic-gate * character of the range (not including the caret). 2772*7c478bd9Sstevel@tonic-gate */ 2773*7c478bd9Sstevel@tonic-gate if(pstr->c == '-') { 2774*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2775*7c478bd9Sstevel@tonic-gate if(c == '-') 2776*7c478bd9Sstevel@tonic-gate matched = 1; 2777*7c478bd9Sstevel@tonic-gate /* 2778*7c478bd9Sstevel@tonic-gate * Skip other leading '-' characters since they make no sense. 2779*7c478bd9Sstevel@tonic-gate */ 2780*7c478bd9Sstevel@tonic-gate while(pstr->c == '-') 2781*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2782*7c478bd9Sstevel@tonic-gate }; 2783*7c478bd9Sstevel@tonic-gate /* 2784*7c478bd9Sstevel@tonic-gate * The hyphen is only a special character when it follows the first 2785*7c478bd9Sstevel@tonic-gate * character of the range (not including the caret or a hyphen). 2786*7c478bd9Sstevel@tonic-gate */ 2787*7c478bd9Sstevel@tonic-gate if(pstr->c == ']') { 2788*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2789*7c478bd9Sstevel@tonic-gate if(c == ']') 2790*7c478bd9Sstevel@tonic-gate matched = 1; 2791*7c478bd9Sstevel@tonic-gate }; 2792*7c478bd9Sstevel@tonic-gate /* 2793*7c478bd9Sstevel@tonic-gate * Having dealt with the characters that have special meanings at 2794*7c478bd9Sstevel@tonic-gate * the beginning of a character range expression, see if the 2795*7c478bd9Sstevel@tonic-gate * character matches any of the remaining characters of the range, 2796*7c478bd9Sstevel@tonic-gate * up until a terminating ']' character is seen. 2797*7c478bd9Sstevel@tonic-gate */ 2798*7c478bd9Sstevel@tonic-gate while(!matched && pstr->c && pstr->c != ']') { 2799*7c478bd9Sstevel@tonic-gate /* 2800*7c478bd9Sstevel@tonic-gate * Is this a range of characters signaled by the two end characters 2801*7c478bd9Sstevel@tonic-gate * separated by a hyphen? 2802*7c478bd9Sstevel@tonic-gate */ 2803*7c478bd9Sstevel@tonic-gate if(pstr->c == '-') { 2804*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); /* Skip the hyphen */ 2805*7c478bd9Sstevel@tonic-gate if(pstr->c != ']') { 2806*7c478bd9Sstevel@tonic-gate if(c >= lastc && c <= pstr->c) 2807*7c478bd9Sstevel@tonic-gate matched = 1; 2808*7c478bd9Sstevel@tonic-gate }; 2809*7c478bd9Sstevel@tonic-gate /* 2810*7c478bd9Sstevel@tonic-gate * A normal character to be compared directly. 2811*7c478bd9Sstevel@tonic-gate */ 2812*7c478bd9Sstevel@tonic-gate } else if(pstr->c == c) { 2813*7c478bd9Sstevel@tonic-gate matched = 1; 2814*7c478bd9Sstevel@tonic-gate }; 2815*7c478bd9Sstevel@tonic-gate /* 2816*7c478bd9Sstevel@tonic-gate * Record and skip the character that we just processed. 2817*7c478bd9Sstevel@tonic-gate */ 2818*7c478bd9Sstevel@tonic-gate lastc = pstr->c; 2819*7c478bd9Sstevel@tonic-gate if(pstr->c != ']') 2820*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2821*7c478bd9Sstevel@tonic-gate }; 2822*7c478bd9Sstevel@tonic-gate /* 2823*7c478bd9Sstevel@tonic-gate * Find the terminating ']'. 2824*7c478bd9Sstevel@tonic-gate */ 2825*7c478bd9Sstevel@tonic-gate while(pstr->c && pstr->c != ']') 2826*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2827*7c478bd9Sstevel@tonic-gate /* 2828*7c478bd9Sstevel@tonic-gate * Did we find a terminating ']'? 2829*7c478bd9Sstevel@tonic-gate */ 2830*7c478bd9Sstevel@tonic-gate if(pstr->c == ']') { 2831*7c478bd9Sstevel@tonic-gate /* 2832*7c478bd9Sstevel@tonic-gate * Skip the terminating ']'. 2833*7c478bd9Sstevel@tonic-gate */ 2834*7c478bd9Sstevel@tonic-gate glh_step_stream(pstr); 2835*7c478bd9Sstevel@tonic-gate /* 2836*7c478bd9Sstevel@tonic-gate * If the pattern started with a caret, invert the sense of the match. 2837*7c478bd9Sstevel@tonic-gate */ 2838*7c478bd9Sstevel@tonic-gate if(invert) 2839*7c478bd9Sstevel@tonic-gate matched = !matched; 2840*7c478bd9Sstevel@tonic-gate /* 2841*7c478bd9Sstevel@tonic-gate * If the pattern didn't end with a ']', then it doesn't match, 2842*7c478bd9Sstevel@tonic-gate * regardless of the value of the required sense of the match. 2843*7c478bd9Sstevel@tonic-gate */ 2844*7c478bd9Sstevel@tonic-gate } else { 2845*7c478bd9Sstevel@tonic-gate matched = 0; 2846*7c478bd9Sstevel@tonic-gate }; 2847*7c478bd9Sstevel@tonic-gate return matched; 2848*7c478bd9Sstevel@tonic-gate } 2849*7c478bd9Sstevel@tonic-gate 2850