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