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