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