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(×tamp)) == 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, ×tamp)) {
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