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