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