17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3*55fea89dSDan Cross  *
47c478bd9Sstevel@tonic-gate  * All rights reserved.
5*55fea89dSDan Cross  *
67c478bd9Sstevel@tonic-gate  * Permission is hereby granted, free of charge, to any person obtaining a
77c478bd9Sstevel@tonic-gate  * copy of this software and associated documentation files (the
87c478bd9Sstevel@tonic-gate  * "Software"), to deal in the Software without restriction, including
97c478bd9Sstevel@tonic-gate  * without limitation the rights to use, copy, modify, merge, publish,
107c478bd9Sstevel@tonic-gate  * distribute, and/or sell copies of the Software, and to permit persons
117c478bd9Sstevel@tonic-gate  * to whom the Software is furnished to do so, provided that the above
127c478bd9Sstevel@tonic-gate  * copyright notice(s) and this permission notice appear in all copies of
137c478bd9Sstevel@tonic-gate  * the Software and that both the above copyright notice(s) and this
147c478bd9Sstevel@tonic-gate  * permission notice appear in supporting documentation.
15*55fea89dSDan Cross  *
167c478bd9Sstevel@tonic-gate  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
177c478bd9Sstevel@tonic-gate  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
187c478bd9Sstevel@tonic-gate  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
197c478bd9Sstevel@tonic-gate  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
207c478bd9Sstevel@tonic-gate  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
217c478bd9Sstevel@tonic-gate  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
227c478bd9Sstevel@tonic-gate  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
237c478bd9Sstevel@tonic-gate  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
247c478bd9Sstevel@tonic-gate  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25*55fea89dSDan Cross  *
267c478bd9Sstevel@tonic-gate  * Except as contained in this notice, the name of a copyright holder
277c478bd9Sstevel@tonic-gate  * shall not be used in advertising or otherwise to promote the sale, use
287c478bd9Sstevel@tonic-gate  * or other dealings in this Software without prior written authorization
297c478bd9Sstevel@tonic-gate  * of the copyright holder.
307c478bd9Sstevel@tonic-gate  */
317c478bd9Sstevel@tonic-gate 
327c478bd9Sstevel@tonic-gate /*
337c478bd9Sstevel@tonic-gate  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
347c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
357c478bd9Sstevel@tonic-gate  */
367c478bd9Sstevel@tonic-gate 
377c478bd9Sstevel@tonic-gate #include <stdlib.h>
387c478bd9Sstevel@tonic-gate #include <stdio.h>
397c478bd9Sstevel@tonic-gate #include <string.h>
407c478bd9Sstevel@tonic-gate #include <ctype.h>
417c478bd9Sstevel@tonic-gate #include <time.h>
427c478bd9Sstevel@tonic-gate #include <errno.h>
437c478bd9Sstevel@tonic-gate 
447c478bd9Sstevel@tonic-gate #include "ioutil.h"
457c478bd9Sstevel@tonic-gate #include "history.h"
467c478bd9Sstevel@tonic-gate #include "freelist.h"
477c478bd9Sstevel@tonic-gate #include "errmsg.h"
487c478bd9Sstevel@tonic-gate 
497c478bd9Sstevel@tonic-gate /*
507c478bd9Sstevel@tonic-gate  * History lines are split into sub-strings of GLH_SEG_SIZE
517c478bd9Sstevel@tonic-gate  * characters.  To avoid wasting space in the GlhLineSeg structure,
527c478bd9Sstevel@tonic-gate  * this should be a multiple of the size of a pointer.
537c478bd9Sstevel@tonic-gate  */
547c478bd9Sstevel@tonic-gate #define GLH_SEG_SIZE 16
557c478bd9Sstevel@tonic-gate 
567c478bd9Sstevel@tonic-gate /*
577c478bd9Sstevel@tonic-gate  * GlhLineSeg structures contain fixed sized segments of a larger
587c478bd9Sstevel@tonic-gate  * string. These are linked into lists to record strings, with all but
597c478bd9Sstevel@tonic-gate  * the last segment having GLH_SEG_SIZE characters. The last segment
607c478bd9Sstevel@tonic-gate  * of a string is terminated within the GLH_SEG_SIZE characters with a
617c478bd9Sstevel@tonic-gate  * '\0'.
627c478bd9Sstevel@tonic-gate  */
637c478bd9Sstevel@tonic-gate typedef struct GlhLineSeg GlhLineSeg;
647c478bd9Sstevel@tonic-gate struct GlhLineSeg {
657c478bd9Sstevel@tonic-gate   GlhLineSeg *next;     /* The next sub-string of the history line */
667c478bd9Sstevel@tonic-gate   char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */
677c478bd9Sstevel@tonic-gate                         /*  substring of a line, as indicated by 'next' */
687c478bd9Sstevel@tonic-gate                         /*  being NULL, is '\0' terminated. */
697c478bd9Sstevel@tonic-gate };
707c478bd9Sstevel@tonic-gate 
717c478bd9Sstevel@tonic-gate /*
727c478bd9Sstevel@tonic-gate  * History lines are recorded in a hash table, such that repeated
737c478bd9Sstevel@tonic-gate  * lines are stored just once.
747c478bd9Sstevel@tonic-gate  *
757c478bd9Sstevel@tonic-gate  * Start by defining the size of the hash table. This should be a
767c478bd9Sstevel@tonic-gate  * prime number.
777c478bd9Sstevel@tonic-gate  */
787c478bd9Sstevel@tonic-gate #define GLH_HASH_SIZE 113
797c478bd9Sstevel@tonic-gate 
807c478bd9Sstevel@tonic-gate typedef struct GlhHashBucket GlhHashBucket;
817c478bd9Sstevel@tonic-gate 
827c478bd9Sstevel@tonic-gate /*
837c478bd9Sstevel@tonic-gate  * Each history line will be represented in the hash table by a
847c478bd9Sstevel@tonic-gate  * structure of the following type.
857c478bd9Sstevel@tonic-gate  */
867c478bd9Sstevel@tonic-gate typedef struct GlhHashNode GlhHashNode;
877c478bd9Sstevel@tonic-gate struct GlhHashNode {
887c478bd9Sstevel@tonic-gate   GlhHashBucket *bucket; /* The parent hash-table bucket of this node */
897c478bd9Sstevel@tonic-gate   GlhHashNode *next;     /* The next in the list of nodes within the */
907c478bd9Sstevel@tonic-gate                          /*  parent hash-table bucket. */
917c478bd9Sstevel@tonic-gate   GlhLineSeg *head;      /* The list of sub-strings which make up a line */
927c478bd9Sstevel@tonic-gate   int len;               /* The length of the line, excluding any '\0' */
937c478bd9Sstevel@tonic-gate   int used;              /* The number of times this string is pointed to by */
947c478bd9Sstevel@tonic-gate                          /*  the time-ordered list of history lines. */
957c478bd9Sstevel@tonic-gate   int reported;          /* A flag that is used when searching to ensure that */
967c478bd9Sstevel@tonic-gate                          /*  a line isn't reported redundantly. */
977c478bd9Sstevel@tonic-gate };
987c478bd9Sstevel@tonic-gate 
997c478bd9Sstevel@tonic-gate /*
1007c478bd9Sstevel@tonic-gate  * How many new GlhHashNode elements should be allocated at a time?
1017c478bd9Sstevel@tonic-gate  */
1027c478bd9Sstevel@tonic-gate #define GLH_HASH_INCR 50
1037c478bd9Sstevel@tonic-gate 
1047c478bd9Sstevel@tonic-gate static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n);
1057c478bd9Sstevel@tonic-gate static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix);
1067c478bd9Sstevel@tonic-gate static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim);
1077c478bd9Sstevel@tonic-gate 
1087c478bd9Sstevel@tonic-gate /*
1097c478bd9Sstevel@tonic-gate  * All history lines which hash to a given bucket in the hash table, are
1107c478bd9Sstevel@tonic-gate  * recorded in a structure of the following type.
1117c478bd9Sstevel@tonic-gate  */
1127c478bd9Sstevel@tonic-gate struct GlhHashBucket {
1137c478bd9Sstevel@tonic-gate   GlhHashNode *lines;  /* The list of history lines which fall in this bucket */
1147c478bd9Sstevel@tonic-gate };
1157c478bd9Sstevel@tonic-gate 
1167c478bd9Sstevel@tonic-gate static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line,
1177c478bd9Sstevel@tonic-gate 				      size_t n);
1187c478bd9Sstevel@tonic-gate static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line,
1197c478bd9Sstevel@tonic-gate 				       size_t n);
1207c478bd9Sstevel@tonic-gate 
1217c478bd9Sstevel@tonic-gate typedef struct {
1227c478bd9Sstevel@tonic-gate   FreeList *node_mem;  /* A free-list of GlhHashNode structures */
1237c478bd9Sstevel@tonic-gate   GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */
1247c478bd9Sstevel@tonic-gate } GlhLineHash;
1257c478bd9Sstevel@tonic-gate 
1267c478bd9Sstevel@tonic-gate /*
1277c478bd9Sstevel@tonic-gate  * GlhLineNode's are used to record history lines in time order.
1287c478bd9Sstevel@tonic-gate  */
1297c478bd9Sstevel@tonic-gate typedef struct GlhLineNode GlhLineNode;
1307c478bd9Sstevel@tonic-gate struct GlhLineNode {
1317c478bd9Sstevel@tonic-gate   long id;             /* The unique identifier of this history line */
1327c478bd9Sstevel@tonic-gate   time_t timestamp;    /* The time at which the line was archived */
1337c478bd9Sstevel@tonic-gate   unsigned group;      /* The identifier of the history group to which the */
1347c478bd9Sstevel@tonic-gate                        /*  the line belongs. */
1357c478bd9Sstevel@tonic-gate   GlhLineNode *next;   /* The next youngest line in the list */
1367c478bd9Sstevel@tonic-gate   GlhLineNode *prev;   /* The next oldest line in the list */
1377c478bd9Sstevel@tonic-gate   GlhHashNode *line;   /* The hash-table entry of the history line */
1387c478bd9Sstevel@tonic-gate };
1397c478bd9Sstevel@tonic-gate 
1407c478bd9Sstevel@tonic-gate /*
1417c478bd9Sstevel@tonic-gate  * The number of GlhLineNode elements per freelist block.
1427c478bd9Sstevel@tonic-gate  */
1437c478bd9Sstevel@tonic-gate #define GLH_LINE_INCR 100
1447c478bd9Sstevel@tonic-gate 
1457c478bd9Sstevel@tonic-gate /*
1467c478bd9Sstevel@tonic-gate  * Encapsulate the time-ordered list of historical lines.
1477c478bd9Sstevel@tonic-gate  */
1487c478bd9Sstevel@tonic-gate typedef struct {
149*55fea89dSDan Cross   FreeList *node_mem;  /* A freelist of GlhLineNode objects */
1507c478bd9Sstevel@tonic-gate   GlhLineNode *head;   /* The oldest line in the list */
1517c478bd9Sstevel@tonic-gate   GlhLineNode *tail;   /* The newest line in the list */
1527c478bd9Sstevel@tonic-gate } GlhLineList;
1537c478bd9Sstevel@tonic-gate 
1547c478bd9Sstevel@tonic-gate /*
1557c478bd9Sstevel@tonic-gate  * The _glh_lookup_history() returns copies of history lines in a
1567c478bd9Sstevel@tonic-gate  * dynamically allocated array. This array is initially allocated
1577c478bd9Sstevel@tonic-gate  * GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be
1587c478bd9Sstevel@tonic-gate  * too small, realloc() is used to increase its size to the required
1597c478bd9Sstevel@tonic-gate  * size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to
1607c478bd9Sstevel@tonic-gate  * reduce the number of realloc() operations needed.
1617c478bd9Sstevel@tonic-gate  */
1627c478bd9Sstevel@tonic-gate #define GLH_LBUF_SIZE 300
1637c478bd9Sstevel@tonic-gate #define GLH_LBUF_MARGIN 100
1647c478bd9Sstevel@tonic-gate 
1657c478bd9Sstevel@tonic-gate /*
1667c478bd9Sstevel@tonic-gate  * Encapsulate all of the resources needed to store historical input lines.
1677c478bd9Sstevel@tonic-gate  */
1687c478bd9Sstevel@tonic-gate struct GlHistory {
1697c478bd9Sstevel@tonic-gate   ErrMsg *err;         /* The error-reporting buffer */
1707c478bd9Sstevel@tonic-gate   GlhLineSeg *buffer;  /* An array of sub-line nodes to be partitioned */
1717c478bd9Sstevel@tonic-gate                        /* into lists of sub-strings recording input lines. */
1727c478bd9Sstevel@tonic-gate   int nbuff;           /* The allocated dimension of buffer[] */
1737c478bd9Sstevel@tonic-gate   GlhLineSeg *unused;  /* The list of free nodes in buffer[] */
1747c478bd9Sstevel@tonic-gate   GlhLineList list;    /* A time ordered list of history lines */
1757c478bd9Sstevel@tonic-gate   GlhLineNode *recall; /* The last line recalled, or NULL if no recall */
1767c478bd9Sstevel@tonic-gate                        /*  session is currently active. */
1777c478bd9Sstevel@tonic-gate   GlhLineNode *id_node;/* The node at which the last ID search terminated */
1787c478bd9Sstevel@tonic-gate   GlhLineHash hash;    /* A hash-table of reference-counted history lines */
1797c478bd9Sstevel@tonic-gate   GlhHashNode *prefix; /* A pointer to a line containing the prefix that */
1807c478bd9Sstevel@tonic-gate                        /*  is being searched for. Note that if prefix==NULL */
1817c478bd9Sstevel@tonic-gate                        /*  and prefix_len>0, this means that no line in */
1827c478bd9Sstevel@tonic-gate                        /*  the buffer starts with the requested prefix. */
1837c478bd9Sstevel@tonic-gate   int prefix_len;      /* The length of the prefix being searched for. */
1847c478bd9Sstevel@tonic-gate   char *lbuf;          /* The array in which _glh_lookup_history() returns */
1857c478bd9Sstevel@tonic-gate                        /*  history lines */
1867c478bd9Sstevel@tonic-gate   int lbuf_dim;        /* The allocated size of lbuf[] */
1877c478bd9Sstevel@tonic-gate   int nbusy;           /* The number of line segments in buffer[] that are */
1887c478bd9Sstevel@tonic-gate                        /*  currently being used to record sub-lines */
1897c478bd9Sstevel@tonic-gate   int nfree;           /* The number of line segments in buffer that are */
1907c478bd9Sstevel@tonic-gate                        /*  not currently being used to record sub-lines */
1917c478bd9Sstevel@tonic-gate   unsigned long seq;   /* The next ID to assign to a line node */
1927c478bd9Sstevel@tonic-gate   unsigned group;      /* The identifier of the current history group */
1937c478bd9Sstevel@tonic-gate   int nline;           /* The number of lines currently in the history list */
1947c478bd9Sstevel@tonic-gate   int max_lines;       /* Either -1 or a ceiling on the number of lines */
1957c478bd9Sstevel@tonic-gate   int enable;          /* If false, ignore history additions and lookups */
1967c478bd9Sstevel@tonic-gate };
1977c478bd9Sstevel@tonic-gate 
1987c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
1997c478bd9Sstevel@tonic-gate static int _glh_cant_load_history(GlHistory *glh, const char *filename,
2007c478bd9Sstevel@tonic-gate 				  int lineno, const char *message, FILE *fp);
2017c478bd9Sstevel@tonic-gate static int _glh_cant_save_history(GlHistory *glh, const char *message,
2027c478bd9Sstevel@tonic-gate 				  const char *filename, FILE *fp);
2037c478bd9Sstevel@tonic-gate static int _glh_write_timestamp(FILE *fp, time_t timestamp);
2047c478bd9Sstevel@tonic-gate static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp);
2057c478bd9Sstevel@tonic-gate #endif
2067c478bd9Sstevel@tonic-gate static void _glh_discard_line(GlHistory *glh, GlhLineNode *node);
2077c478bd9Sstevel@tonic-gate static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id);
2087c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line,
2097c478bd9Sstevel@tonic-gate 				      size_t n);
2107c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode);
2117c478bd9Sstevel@tonic-gate static int _glh_prepare_for_recall(GlHistory *glh, char *line);
2127c478bd9Sstevel@tonic-gate 
2137c478bd9Sstevel@tonic-gate /*
2147c478bd9Sstevel@tonic-gate  * The following structure and functions are used to iterate through
2157c478bd9Sstevel@tonic-gate  * the characters of a segmented history line.
2167c478bd9Sstevel@tonic-gate  */
2177c478bd9Sstevel@tonic-gate typedef struct {
2187c478bd9Sstevel@tonic-gate   GlhLineSeg *seg;  /* The line segment that the next character will */
2197c478bd9Sstevel@tonic-gate                     /*  be returned from. */
2207c478bd9Sstevel@tonic-gate   int posn;         /* The index in the above line segment, containing */
2217c478bd9Sstevel@tonic-gate                     /*  the next unread character. */
2227c478bd9Sstevel@tonic-gate   char c;           /* The current character in the input line */
2237c478bd9Sstevel@tonic-gate } GlhLineStream;
2247c478bd9Sstevel@tonic-gate static void glh_init_stream(GlhLineStream *str, GlhHashNode *line);
2257c478bd9Sstevel@tonic-gate static void glh_step_stream(GlhLineStream *str);
2267c478bd9Sstevel@tonic-gate 
2277c478bd9Sstevel@tonic-gate /*
2287c478bd9Sstevel@tonic-gate  * See if search prefix contains any globbing characters.
2297c478bd9Sstevel@tonic-gate  */
2307c478bd9Sstevel@tonic-gate static int glh_contains_glob(GlhHashNode *prefix);
2317c478bd9Sstevel@tonic-gate /*
2327c478bd9Sstevel@tonic-gate  * Match a line against a search pattern.
2337c478bd9Sstevel@tonic-gate  */
2347c478bd9Sstevel@tonic-gate static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr);
2357c478bd9Sstevel@tonic-gate static int glh_matches_range(char c, GlhLineStream *pstr);
2367c478bd9Sstevel@tonic-gate 
2377c478bd9Sstevel@tonic-gate /*.......................................................................
2387c478bd9Sstevel@tonic-gate  * Create a line history maintenance object.
2397c478bd9Sstevel@tonic-gate  *
2407c478bd9Sstevel@tonic-gate  * Input:
2417c478bd9Sstevel@tonic-gate  *  buflen     size_t    The number of bytes to allocate to the
2427c478bd9Sstevel@tonic-gate  *                       buffer that is used to record all of the
2437c478bd9Sstevel@tonic-gate  *                       most recent lines of user input that will fit.
2447c478bd9Sstevel@tonic-gate  *                       If buflen==0, no buffer will be allocated.
2457c478bd9Sstevel@tonic-gate  * Output:
2467c478bd9Sstevel@tonic-gate  *  return  GlHistory *  The new object, or NULL on error.
2477c478bd9Sstevel@tonic-gate  */
_new_GlHistory(size_t buflen)2487c478bd9Sstevel@tonic-gate GlHistory *_new_GlHistory(size_t buflen)
2497c478bd9Sstevel@tonic-gate {
2507c478bd9Sstevel@tonic-gate   GlHistory *glh;  /* The object to be returned */
2517c478bd9Sstevel@tonic-gate   int i;
2527c478bd9Sstevel@tonic-gate /*
2537c478bd9Sstevel@tonic-gate  * Allocate the container.
2547c478bd9Sstevel@tonic-gate  */
2557c478bd9Sstevel@tonic-gate   glh = (GlHistory *) malloc(sizeof(GlHistory));
2567c478bd9Sstevel@tonic-gate   if(!glh) {
2577c478bd9Sstevel@tonic-gate     errno = ENOMEM;
2587c478bd9Sstevel@tonic-gate     return NULL;
2597c478bd9Sstevel@tonic-gate   };
2607c478bd9Sstevel@tonic-gate /*
2617c478bd9Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
2627c478bd9Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
2637c478bd9Sstevel@tonic-gate  * to _del_GlHistory().
2647c478bd9Sstevel@tonic-gate  */
2657c478bd9Sstevel@tonic-gate   glh->err = NULL;
2667c478bd9Sstevel@tonic-gate   glh->buffer = NULL;
2677c478bd9Sstevel@tonic-gate   glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE;
2687c478bd9Sstevel@tonic-gate   glh->unused = NULL;
2697c478bd9Sstevel@tonic-gate   glh->list.node_mem = NULL;
2707c478bd9Sstevel@tonic-gate   glh->list.head = glh->list.tail = NULL;
2717c478bd9Sstevel@tonic-gate   glh->recall = NULL;
2727c478bd9Sstevel@tonic-gate   glh->id_node = NULL;
2737c478bd9Sstevel@tonic-gate   glh->hash.node_mem = NULL;
2747c478bd9Sstevel@tonic-gate   for(i=0; i<GLH_HASH_SIZE; i++)
2757c478bd9Sstevel@tonic-gate     glh->hash.bucket[i].lines = NULL;
2767c478bd9Sstevel@tonic-gate   glh->prefix = NULL;
2777c478bd9Sstevel@tonic-gate   glh->lbuf = NULL;
2787c478bd9Sstevel@tonic-gate   glh->lbuf_dim = 0;
2797c478bd9Sstevel@tonic-gate   glh->nbusy = 0;
2807c478bd9Sstevel@tonic-gate   glh->nfree = glh->nbuff;
2817c478bd9Sstevel@tonic-gate   glh->seq = 0;
2827c478bd9Sstevel@tonic-gate   glh->group = 0;
2837c478bd9Sstevel@tonic-gate   glh->nline = 0;
2847c478bd9Sstevel@tonic-gate   glh->max_lines = -1;
2857c478bd9Sstevel@tonic-gate   glh->enable = 1;
2867c478bd9Sstevel@tonic-gate /*
2877c478bd9Sstevel@tonic-gate  * Allocate a place to record error messages.
2887c478bd9Sstevel@tonic-gate  */
2897c478bd9Sstevel@tonic-gate   glh->err = _new_ErrMsg();
2907c478bd9Sstevel@tonic-gate   if(!glh->err)
2917c478bd9Sstevel@tonic-gate     return _del_GlHistory(glh);
2927c478bd9Sstevel@tonic-gate /*
2937c478bd9Sstevel@tonic-gate  * Allocate the buffer, if required.
2947c478bd9Sstevel@tonic-gate  */
2957c478bd9Sstevel@tonic-gate   if(glh->nbuff > 0) {
2967c478bd9Sstevel@tonic-gate     glh->nbuff = glh->nfree;
2977c478bd9Sstevel@tonic-gate     glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff);
2987c478bd9Sstevel@tonic-gate     if(!glh->buffer) {
2997c478bd9Sstevel@tonic-gate       errno = ENOMEM;
3007c478bd9Sstevel@tonic-gate       return _del_GlHistory(glh);
3017c478bd9Sstevel@tonic-gate     };
3027c478bd9Sstevel@tonic-gate /*
3037c478bd9Sstevel@tonic-gate  * All nodes of the buffer are currently unused, so link them all into
3047c478bd9Sstevel@tonic-gate  * a list and make glh->unused point to the head of this list.
3057c478bd9Sstevel@tonic-gate  */
3067c478bd9Sstevel@tonic-gate     glh->unused = glh->buffer;
3077c478bd9Sstevel@tonic-gate     for(i=0; i<glh->nbuff-1; i++) {
3087c478bd9Sstevel@tonic-gate       GlhLineSeg *seg = glh->unused + i;
3097c478bd9Sstevel@tonic-gate       seg->next = seg + 1;
3107c478bd9Sstevel@tonic-gate     };
3117c478bd9Sstevel@tonic-gate     glh->unused[i].next = NULL;
3127c478bd9Sstevel@tonic-gate   };
3137c478bd9Sstevel@tonic-gate /*
3147c478bd9Sstevel@tonic-gate  * Allocate the GlhLineNode freelist.
3157c478bd9Sstevel@tonic-gate  */
3167c478bd9Sstevel@tonic-gate   glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR);
3177c478bd9Sstevel@tonic-gate   if(!glh->list.node_mem)
3187c478bd9Sstevel@tonic-gate     return _del_GlHistory(glh);
3197c478bd9Sstevel@tonic-gate /*
3207c478bd9Sstevel@tonic-gate  * Allocate the GlhHashNode freelist.
3217c478bd9Sstevel@tonic-gate  */
3227c478bd9Sstevel@tonic-gate   glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR);
3237c478bd9Sstevel@tonic-gate   if(!glh->hash.node_mem)
3247c478bd9Sstevel@tonic-gate     return _del_GlHistory(glh);
3257c478bd9Sstevel@tonic-gate /*
3267c478bd9Sstevel@tonic-gate  * Allocate the array that _glh_lookup_history() uses to return a
3277c478bd9Sstevel@tonic-gate  * copy of a given history line. This will be resized when necessary.
3287c478bd9Sstevel@tonic-gate  */
3297c478bd9Sstevel@tonic-gate   glh->lbuf_dim = GLH_LBUF_SIZE;
3307c478bd9Sstevel@tonic-gate   glh->lbuf = (char *) malloc(glh->lbuf_dim);
3317c478bd9Sstevel@tonic-gate   if(!glh->lbuf) {
3327c478bd9Sstevel@tonic-gate     errno = ENOMEM;
3337c478bd9Sstevel@tonic-gate     return _del_GlHistory(glh);
3347c478bd9Sstevel@tonic-gate   };
3357c478bd9Sstevel@tonic-gate   return glh;
3367c478bd9Sstevel@tonic-gate }
3377c478bd9Sstevel@tonic-gate 
3387c478bd9Sstevel@tonic-gate /*.......................................................................
3397c478bd9Sstevel@tonic-gate  * Delete a GlHistory object.
3407c478bd9Sstevel@tonic-gate  *
3417c478bd9Sstevel@tonic-gate  * Input:
3427c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The object to be deleted.
3437c478bd9Sstevel@tonic-gate  * Output:
3447c478bd9Sstevel@tonic-gate  *  return GlHistory *  The deleted object (always NULL).
3457c478bd9Sstevel@tonic-gate  */
_del_GlHistory(GlHistory * glh)3467c478bd9Sstevel@tonic-gate GlHistory *_del_GlHistory(GlHistory *glh)
3477c478bd9Sstevel@tonic-gate {
3487c478bd9Sstevel@tonic-gate   if(glh) {
3497c478bd9Sstevel@tonic-gate /*
3507c478bd9Sstevel@tonic-gate  * Delete the error-message buffer.
3517c478bd9Sstevel@tonic-gate  */
3527c478bd9Sstevel@tonic-gate     glh->err = _del_ErrMsg(glh->err);
3537c478bd9Sstevel@tonic-gate /*
3547c478bd9Sstevel@tonic-gate  * Delete the buffer.
3557c478bd9Sstevel@tonic-gate  */
3567c478bd9Sstevel@tonic-gate     if(glh->buffer) {
3577c478bd9Sstevel@tonic-gate       free(glh->buffer);
3587c478bd9Sstevel@tonic-gate       glh->buffer = NULL;
3597c478bd9Sstevel@tonic-gate       glh->unused = NULL;
3607c478bd9Sstevel@tonic-gate     };
3617c478bd9Sstevel@tonic-gate /*
3627c478bd9Sstevel@tonic-gate  * Delete the freelist of GlhLineNode's.
3637c478bd9Sstevel@tonic-gate  */
3647c478bd9Sstevel@tonic-gate     glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1);
3657c478bd9Sstevel@tonic-gate /*
3667c478bd9Sstevel@tonic-gate  * The contents of the list were deleted by deleting the freelist.
3677c478bd9Sstevel@tonic-gate  */
3687c478bd9Sstevel@tonic-gate     glh->list.head = NULL;
3697c478bd9Sstevel@tonic-gate     glh->list.tail = NULL;
3707c478bd9Sstevel@tonic-gate /*
3717c478bd9Sstevel@tonic-gate  * Delete the freelist of GlhHashNode's.
3727c478bd9Sstevel@tonic-gate  */
3737c478bd9Sstevel@tonic-gate     glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1);
3747c478bd9Sstevel@tonic-gate /*
3757c478bd9Sstevel@tonic-gate  * Delete the lookup buffer.
3767c478bd9Sstevel@tonic-gate  */
3777c478bd9Sstevel@tonic-gate     if(glh->lbuf)
3787c478bd9Sstevel@tonic-gate       free(glh->lbuf);
3797c478bd9Sstevel@tonic-gate /*
3807c478bd9Sstevel@tonic-gate  * Delete the container.
3817c478bd9Sstevel@tonic-gate  */
3827c478bd9Sstevel@tonic-gate     free(glh);
3837c478bd9Sstevel@tonic-gate   };
3847c478bd9Sstevel@tonic-gate   return NULL;
3857c478bd9Sstevel@tonic-gate }
3867c478bd9Sstevel@tonic-gate 
3877c478bd9Sstevel@tonic-gate /*.......................................................................
3887c478bd9Sstevel@tonic-gate  * Append a new line to the history list, deleting old lines to make
3897c478bd9Sstevel@tonic-gate  * room, if needed.
3907c478bd9Sstevel@tonic-gate  *
3917c478bd9Sstevel@tonic-gate  * Input:
3927c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
3937c478bd9Sstevel@tonic-gate  *  line      char *  The line to be archived.
3947c478bd9Sstevel@tonic-gate  *  force      int    Unless this flag is non-zero, empty lines aren't
3957c478bd9Sstevel@tonic-gate  *                    archived. This flag requests that the line be
3967c478bd9Sstevel@tonic-gate  *                    archived regardless.
3977c478bd9Sstevel@tonic-gate  * Output:
3987c478bd9Sstevel@tonic-gate  *  return     int    0 - OK.
3997c478bd9Sstevel@tonic-gate  *                    1 - Error.
4007c478bd9Sstevel@tonic-gate  */
_glh_add_history(GlHistory * glh,const char * line,int force)4017c478bd9Sstevel@tonic-gate int _glh_add_history(GlHistory *glh, const char *line, int force)
4027c478bd9Sstevel@tonic-gate {
4037c478bd9Sstevel@tonic-gate   int slen;         /* The length of the line to be recorded (minus the '\0') */
4047c478bd9Sstevel@tonic-gate   int empty;          /* True if the string is empty */
4057c478bd9Sstevel@tonic-gate   const char *nlptr;  /* A pointer to a newline character in line[] */
4067c478bd9Sstevel@tonic-gate   GlhHashNode *hnode; /* The hash-table node of the line */
4077c478bd9Sstevel@tonic-gate   GlhLineNode *lnode; /* A node in the time-ordered list of lines */
4087c478bd9Sstevel@tonic-gate   int i;
4097c478bd9Sstevel@tonic-gate /*
4107c478bd9Sstevel@tonic-gate  * Check the arguments.
4117c478bd9Sstevel@tonic-gate  */
4127c478bd9Sstevel@tonic-gate   if(!glh || !line) {
4137c478bd9Sstevel@tonic-gate     errno = EINVAL;
4147c478bd9Sstevel@tonic-gate     return 1;
4157c478bd9Sstevel@tonic-gate   };
4167c478bd9Sstevel@tonic-gate /*
4177c478bd9Sstevel@tonic-gate  * Is history enabled?
4187c478bd9Sstevel@tonic-gate  */
4197c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
4207c478bd9Sstevel@tonic-gate     return 0;
4217c478bd9Sstevel@tonic-gate /*
4227c478bd9Sstevel@tonic-gate  * Cancel any ongoing search.
4237c478bd9Sstevel@tonic-gate  */
4247c478bd9Sstevel@tonic-gate   if(_glh_cancel_search(glh))
4257c478bd9Sstevel@tonic-gate     return 1;
4267c478bd9Sstevel@tonic-gate /*
4277c478bd9Sstevel@tonic-gate  * How long is the string to be recorded, being careful not to include
4287c478bd9Sstevel@tonic-gate  * any terminating '\n' character.
4297c478bd9Sstevel@tonic-gate  */
4307c478bd9Sstevel@tonic-gate   nlptr = strchr(line, '\n');
4317c478bd9Sstevel@tonic-gate   if(nlptr)
4327c478bd9Sstevel@tonic-gate     slen = (nlptr - line);
4337c478bd9Sstevel@tonic-gate   else
4347c478bd9Sstevel@tonic-gate     slen = strlen(line);
4357c478bd9Sstevel@tonic-gate /*
4367c478bd9Sstevel@tonic-gate  * Is the line empty?
4377c478bd9Sstevel@tonic-gate  */
4387c478bd9Sstevel@tonic-gate   empty = 1;
4397c478bd9Sstevel@tonic-gate   for(i=0; i<slen && empty; i++)
4407c478bd9Sstevel@tonic-gate     empty = isspace((int)(unsigned char) line[i]);
4417c478bd9Sstevel@tonic-gate /*
4427c478bd9Sstevel@tonic-gate  * If the line is empty, don't add it to the buffer unless explicitly
4437c478bd9Sstevel@tonic-gate  * told to.
4447c478bd9Sstevel@tonic-gate  */
4457c478bd9Sstevel@tonic-gate   if(empty && !force)
4467c478bd9Sstevel@tonic-gate     return 0;
4477c478bd9Sstevel@tonic-gate /*
4487c478bd9Sstevel@tonic-gate  * Has an upper limit to the number of lines in the history list been
4497c478bd9Sstevel@tonic-gate  * specified?
4507c478bd9Sstevel@tonic-gate  */
4517c478bd9Sstevel@tonic-gate   if(glh->max_lines >= 0) {
4527c478bd9Sstevel@tonic-gate /*
4537c478bd9Sstevel@tonic-gate  * If necessary, remove old lines until there is room to add one new
4547c478bd9Sstevel@tonic-gate  * line without exceeding the specified line limit.
4557c478bd9Sstevel@tonic-gate  */
4567c478bd9Sstevel@tonic-gate     while(glh->nline > 0 && glh->nline >= glh->max_lines)
4577c478bd9Sstevel@tonic-gate       _glh_discard_line(glh, glh->list.head);
4587c478bd9Sstevel@tonic-gate /*
4597c478bd9Sstevel@tonic-gate  * We can't archive the line if the maximum number of lines allowed is
4607c478bd9Sstevel@tonic-gate  * zero.
4617c478bd9Sstevel@tonic-gate  */
4627c478bd9Sstevel@tonic-gate     if(glh->max_lines == 0)
4637c478bd9Sstevel@tonic-gate       return 0;
4647c478bd9Sstevel@tonic-gate   };
4657c478bd9Sstevel@tonic-gate /*
4667c478bd9Sstevel@tonic-gate  * Unless already stored, store a copy of the line in the history buffer,
4677c478bd9Sstevel@tonic-gate  * then return a reference-counted hash-node pointer to this copy.
4687c478bd9Sstevel@tonic-gate  */
4697c478bd9Sstevel@tonic-gate   hnode = _glh_acquire_copy(glh, line, slen);
4707c478bd9Sstevel@tonic-gate   if(!hnode) {
4717c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG);
4727c478bd9Sstevel@tonic-gate     errno = ENOMEM;
4737c478bd9Sstevel@tonic-gate     return 1;
4747c478bd9Sstevel@tonic-gate   };
4757c478bd9Sstevel@tonic-gate /*
4767c478bd9Sstevel@tonic-gate  * Allocate a new node in the time-ordered list of lines.
4777c478bd9Sstevel@tonic-gate  */
4787c478bd9Sstevel@tonic-gate   lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem);
4797c478bd9Sstevel@tonic-gate /*
4807c478bd9Sstevel@tonic-gate  * If a new line-node couldn't be allocated, discard our copy of the
4817c478bd9Sstevel@tonic-gate  * stored line before reporting the error.
4827c478bd9Sstevel@tonic-gate  */
4837c478bd9Sstevel@tonic-gate   if(!lnode) {
4847c478bd9Sstevel@tonic-gate     hnode = _glh_discard_copy(glh, hnode);
4857c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "No room to store history line", END_ERR_MSG);
4867c478bd9Sstevel@tonic-gate     errno = ENOMEM;
4877c478bd9Sstevel@tonic-gate     return 1;
4887c478bd9Sstevel@tonic-gate   };
4897c478bd9Sstevel@tonic-gate /*
4907c478bd9Sstevel@tonic-gate  * Record a pointer to the hash-table record of the line in the new
4917c478bd9Sstevel@tonic-gate  * list node.
4927c478bd9Sstevel@tonic-gate  */
4937c478bd9Sstevel@tonic-gate   lnode->id = glh->seq++;
4947c478bd9Sstevel@tonic-gate   lnode->timestamp = time(NULL);
4957c478bd9Sstevel@tonic-gate   lnode->group = glh->group;
4967c478bd9Sstevel@tonic-gate   lnode->line = hnode;
4977c478bd9Sstevel@tonic-gate /*
4987c478bd9Sstevel@tonic-gate  * Append the new node to the end of the time-ordered list.
4997c478bd9Sstevel@tonic-gate  */
5007c478bd9Sstevel@tonic-gate   if(glh->list.head)
5017c478bd9Sstevel@tonic-gate     glh->list.tail->next = lnode;
5027c478bd9Sstevel@tonic-gate   else
5037c478bd9Sstevel@tonic-gate     glh->list.head = lnode;
5047c478bd9Sstevel@tonic-gate   lnode->next = NULL;
5057c478bd9Sstevel@tonic-gate   lnode->prev = glh->list.tail;
5067c478bd9Sstevel@tonic-gate   glh->list.tail = lnode;
5077c478bd9Sstevel@tonic-gate /*
5087c478bd9Sstevel@tonic-gate  * Record the addition of a line to the list.
5097c478bd9Sstevel@tonic-gate  */
5107c478bd9Sstevel@tonic-gate   glh->nline++;
5117c478bd9Sstevel@tonic-gate   return 0;
5127c478bd9Sstevel@tonic-gate }
5137c478bd9Sstevel@tonic-gate 
5147c478bd9Sstevel@tonic-gate /*.......................................................................
5157c478bd9Sstevel@tonic-gate  * Recall the next oldest line that has the search prefix last recorded
5167c478bd9Sstevel@tonic-gate  * by _glh_search_prefix().
5177c478bd9Sstevel@tonic-gate  *
5187c478bd9Sstevel@tonic-gate  * Input:
5197c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
5207c478bd9Sstevel@tonic-gate  *  line      char *  The input line buffer. On input this should contain
5217c478bd9Sstevel@tonic-gate  *                    the current input line, and on output, if anything
5227c478bd9Sstevel@tonic-gate  *                    was found, its contents will have been replaced
5237c478bd9Sstevel@tonic-gate  *                    with the matching line.
5247c478bd9Sstevel@tonic-gate  *  dim     size_t    The allocated dimension of the line buffer.
5257c478bd9Sstevel@tonic-gate  * Output:
5267c478bd9Sstevel@tonic-gate  *  return    char *  A pointer to line[0], or NULL if not found.
5277c478bd9Sstevel@tonic-gate  */
_glh_find_backwards(GlHistory * glh,char * line,size_t dim)5287c478bd9Sstevel@tonic-gate char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim)
5297c478bd9Sstevel@tonic-gate {
5307c478bd9Sstevel@tonic-gate   GlhLineNode *node;     /* The line location node being checked */
5317c478bd9Sstevel@tonic-gate   GlhHashNode *old_line; /* The previous recalled line */
5327c478bd9Sstevel@tonic-gate /*
5337c478bd9Sstevel@tonic-gate  * Check the arguments.
5347c478bd9Sstevel@tonic-gate  */
5357c478bd9Sstevel@tonic-gate   if(!glh || !line) {
5367c478bd9Sstevel@tonic-gate     if(glh)
5377c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
5387c478bd9Sstevel@tonic-gate     errno = EINVAL;
5397c478bd9Sstevel@tonic-gate     return NULL;
5407c478bd9Sstevel@tonic-gate   };
5417c478bd9Sstevel@tonic-gate /*
5427c478bd9Sstevel@tonic-gate  * Is history enabled?
5437c478bd9Sstevel@tonic-gate  */
5447c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
5457c478bd9Sstevel@tonic-gate     return NULL;
5467c478bd9Sstevel@tonic-gate /*
5477c478bd9Sstevel@tonic-gate  * Check the line dimensions.
5487c478bd9Sstevel@tonic-gate  */
5497c478bd9Sstevel@tonic-gate   if(dim < strlen(line) + 1) {
5507c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
5517c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
5527c478bd9Sstevel@tonic-gate     errno = EINVAL;
5537c478bd9Sstevel@tonic-gate     return NULL;
5547c478bd9Sstevel@tonic-gate   };
5557c478bd9Sstevel@tonic-gate /*
5567c478bd9Sstevel@tonic-gate  * Preserve the input line if needed.
5577c478bd9Sstevel@tonic-gate  */
5587c478bd9Sstevel@tonic-gate   if(_glh_prepare_for_recall(glh, line))
5597c478bd9Sstevel@tonic-gate     return NULL;
5607c478bd9Sstevel@tonic-gate /*
5617c478bd9Sstevel@tonic-gate  * From where should we start the search?
5627c478bd9Sstevel@tonic-gate  */
5637c478bd9Sstevel@tonic-gate   if(glh->recall) {
5647c478bd9Sstevel@tonic-gate     node = glh->recall->prev;
5657c478bd9Sstevel@tonic-gate     old_line = glh->recall->line;
5667c478bd9Sstevel@tonic-gate   } else {
5677c478bd9Sstevel@tonic-gate     node = glh->list.tail;
5687c478bd9Sstevel@tonic-gate     old_line = NULL;
5697c478bd9Sstevel@tonic-gate   };
5707c478bd9Sstevel@tonic-gate /*
5717c478bd9Sstevel@tonic-gate  * Search backwards through the list for the first match with the
5727c478bd9Sstevel@tonic-gate  * prefix string that differs from the last line that was recalled.
5737c478bd9Sstevel@tonic-gate  */
5747c478bd9Sstevel@tonic-gate   while(node && (node->group != glh->group || node->line == old_line ||
5757c478bd9Sstevel@tonic-gate 	  !_glh_line_matches_prefix(node->line, glh->prefix)))
5767c478bd9Sstevel@tonic-gate     node = node->prev;
5777c478bd9Sstevel@tonic-gate /*
5787c478bd9Sstevel@tonic-gate  * Was a matching line found?
5797c478bd9Sstevel@tonic-gate  */
5807c478bd9Sstevel@tonic-gate   if(node) {
5817c478bd9Sstevel@tonic-gate /*
5827c478bd9Sstevel@tonic-gate  * Recall the found node as the starting point for subsequent
5837c478bd9Sstevel@tonic-gate  * searches.
5847c478bd9Sstevel@tonic-gate  */
5857c478bd9Sstevel@tonic-gate     glh->recall = node;
5867c478bd9Sstevel@tonic-gate /*
5877c478bd9Sstevel@tonic-gate  * Copy the matching line into the provided line buffer.
5887c478bd9Sstevel@tonic-gate  */
5897c478bd9Sstevel@tonic-gate     _glh_return_line(node->line, line, dim);
5907c478bd9Sstevel@tonic-gate /*
5917c478bd9Sstevel@tonic-gate  * Return it.
5927c478bd9Sstevel@tonic-gate  */
5937c478bd9Sstevel@tonic-gate     return line;
5947c478bd9Sstevel@tonic-gate   };
5957c478bd9Sstevel@tonic-gate /*
5967c478bd9Sstevel@tonic-gate  * No match was found.
5977c478bd9Sstevel@tonic-gate  */
5987c478bd9Sstevel@tonic-gate   return NULL;
5997c478bd9Sstevel@tonic-gate }
6007c478bd9Sstevel@tonic-gate 
6017c478bd9Sstevel@tonic-gate /*.......................................................................
6027c478bd9Sstevel@tonic-gate  * Recall the next newest line that has the search prefix last recorded
6037c478bd9Sstevel@tonic-gate  * by _glh_search_prefix().
6047c478bd9Sstevel@tonic-gate  *
6057c478bd9Sstevel@tonic-gate  * Input:
6067c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
6077c478bd9Sstevel@tonic-gate  *  line      char *  The input line buffer. On input this should contain
6087c478bd9Sstevel@tonic-gate  *                    the current input line, and on output, if anything
6097c478bd9Sstevel@tonic-gate  *                    was found, its contents will have been replaced
6107c478bd9Sstevel@tonic-gate  *                    with the matching line.
6117c478bd9Sstevel@tonic-gate  *  dim     size_t    The allocated dimensions of the line buffer.
6127c478bd9Sstevel@tonic-gate  * Output:
6137c478bd9Sstevel@tonic-gate  *  return    char *  The line requested, or NULL if no matching line
6147c478bd9Sstevel@tonic-gate  *                    was found.
6157c478bd9Sstevel@tonic-gate  */
_glh_find_forwards(GlHistory * glh,char * line,size_t dim)6167c478bd9Sstevel@tonic-gate char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim)
6177c478bd9Sstevel@tonic-gate {
6187c478bd9Sstevel@tonic-gate   GlhLineNode *node;     /* The line location node being checked */
6197c478bd9Sstevel@tonic-gate   GlhHashNode *old_line; /* The previous recalled line */
6207c478bd9Sstevel@tonic-gate /*
6217c478bd9Sstevel@tonic-gate  * Check the arguments.
6227c478bd9Sstevel@tonic-gate  */
6237c478bd9Sstevel@tonic-gate   if(!glh || !line) {
6247c478bd9Sstevel@tonic-gate     if(glh)
6257c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
6267c478bd9Sstevel@tonic-gate     errno = EINVAL;
6277c478bd9Sstevel@tonic-gate     return NULL;
6287c478bd9Sstevel@tonic-gate   };
6297c478bd9Sstevel@tonic-gate /*
6307c478bd9Sstevel@tonic-gate  * Is history enabled?
6317c478bd9Sstevel@tonic-gate  */
6327c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
6337c478bd9Sstevel@tonic-gate     return NULL;
6347c478bd9Sstevel@tonic-gate /*
6357c478bd9Sstevel@tonic-gate  * Check the line dimensions.
6367c478bd9Sstevel@tonic-gate  */
6377c478bd9Sstevel@tonic-gate   if(dim < strlen(line) + 1) {
6387c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
6397c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
6407c478bd9Sstevel@tonic-gate     errno = EINVAL;
6417c478bd9Sstevel@tonic-gate     return NULL;
6427c478bd9Sstevel@tonic-gate   };
6437c478bd9Sstevel@tonic-gate /*
6447c478bd9Sstevel@tonic-gate  * From where should we start the search?
6457c478bd9Sstevel@tonic-gate  */
6467c478bd9Sstevel@tonic-gate   if(glh->recall) {
6477c478bd9Sstevel@tonic-gate     node = glh->recall->next;
6487c478bd9Sstevel@tonic-gate     old_line = glh->recall->line;
6497c478bd9Sstevel@tonic-gate   } else {
6507c478bd9Sstevel@tonic-gate     return NULL;
6517c478bd9Sstevel@tonic-gate   };
6527c478bd9Sstevel@tonic-gate /*
6537c478bd9Sstevel@tonic-gate  * Search forwards through the list for the first match with the
6547c478bd9Sstevel@tonic-gate  * prefix string.
6557c478bd9Sstevel@tonic-gate  */
6567c478bd9Sstevel@tonic-gate   while(node && (node->group != glh->group || node->line == old_line ||
6577c478bd9Sstevel@tonic-gate 	  !_glh_line_matches_prefix(node->line, glh->prefix)))
6587c478bd9Sstevel@tonic-gate     node = node->next;
6597c478bd9Sstevel@tonic-gate /*
6607c478bd9Sstevel@tonic-gate  * Was a matching line found?
6617c478bd9Sstevel@tonic-gate  */
6627c478bd9Sstevel@tonic-gate   if(node) {
6637c478bd9Sstevel@tonic-gate /*
6647c478bd9Sstevel@tonic-gate  * Copy the matching line into the provided line buffer.
6657c478bd9Sstevel@tonic-gate  */
6667c478bd9Sstevel@tonic-gate     _glh_return_line(node->line, line, dim);
6677c478bd9Sstevel@tonic-gate /*
6687c478bd9Sstevel@tonic-gate  * Record the starting point of the next search.
6697c478bd9Sstevel@tonic-gate  */
6707c478bd9Sstevel@tonic-gate     glh->recall = node;
6717c478bd9Sstevel@tonic-gate /*
6727c478bd9Sstevel@tonic-gate  * If we just returned the line that was being entered when the search
6737c478bd9Sstevel@tonic-gate  * session first started, cancel the search.
6747c478bd9Sstevel@tonic-gate  */
6757c478bd9Sstevel@tonic-gate     if(node == glh->list.tail)
6767c478bd9Sstevel@tonic-gate       _glh_cancel_search(glh);
6777c478bd9Sstevel@tonic-gate /*
6787c478bd9Sstevel@tonic-gate  * Return the matching line to the user.
6797c478bd9Sstevel@tonic-gate  */
6807c478bd9Sstevel@tonic-gate     return line;
6817c478bd9Sstevel@tonic-gate   };
6827c478bd9Sstevel@tonic-gate /*
6837c478bd9Sstevel@tonic-gate  * No match was found.
6847c478bd9Sstevel@tonic-gate  */
6857c478bd9Sstevel@tonic-gate   return NULL;
6867c478bd9Sstevel@tonic-gate }
6877c478bd9Sstevel@tonic-gate 
6887c478bd9Sstevel@tonic-gate /*.......................................................................
6897c478bd9Sstevel@tonic-gate  * If a search is in progress, cancel it.
6907c478bd9Sstevel@tonic-gate  *
6917c478bd9Sstevel@tonic-gate  * This involves discarding the line that was temporarily saved by
6927c478bd9Sstevel@tonic-gate  * _glh_find_backwards() when the search was originally started,
6937c478bd9Sstevel@tonic-gate  * and reseting the search iteration pointer to NULL.
6947c478bd9Sstevel@tonic-gate  *
6957c478bd9Sstevel@tonic-gate  * Input:
6967c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
6977c478bd9Sstevel@tonic-gate  * Output:
6987c478bd9Sstevel@tonic-gate  *  return     int    0 - OK.
6997c478bd9Sstevel@tonic-gate  *                    1 - Error.
7007c478bd9Sstevel@tonic-gate  */
_glh_cancel_search(GlHistory * glh)7017c478bd9Sstevel@tonic-gate int _glh_cancel_search(GlHistory *glh)
7027c478bd9Sstevel@tonic-gate {
7037c478bd9Sstevel@tonic-gate /*
7047c478bd9Sstevel@tonic-gate  * Check the arguments.
7057c478bd9Sstevel@tonic-gate  */
7067c478bd9Sstevel@tonic-gate   if(!glh) {
7077c478bd9Sstevel@tonic-gate     errno = EINVAL;
7087c478bd9Sstevel@tonic-gate     return 1;
7097c478bd9Sstevel@tonic-gate   };
7107c478bd9Sstevel@tonic-gate /*
7117c478bd9Sstevel@tonic-gate  * If there wasn't a search in progress, do nothing.
7127c478bd9Sstevel@tonic-gate  */
7137c478bd9Sstevel@tonic-gate   if(!glh->recall)
7147c478bd9Sstevel@tonic-gate     return 0;
7157c478bd9Sstevel@tonic-gate /*
7167c478bd9Sstevel@tonic-gate  * Reset the search pointers. Note that it is essential to set
7177c478bd9Sstevel@tonic-gate  * glh->recall to NULL before calling _glh_discard_line(), to avoid an
7187c478bd9Sstevel@tonic-gate  * infinite recursion.
7197c478bd9Sstevel@tonic-gate  */
7207c478bd9Sstevel@tonic-gate   glh->recall = NULL;
7217c478bd9Sstevel@tonic-gate /*
7227c478bd9Sstevel@tonic-gate  * Delete the node of the preserved line.
7237c478bd9Sstevel@tonic-gate  */
7247c478bd9Sstevel@tonic-gate   _glh_discard_line(glh, glh->list.tail);
7257c478bd9Sstevel@tonic-gate   return 0;
7267c478bd9Sstevel@tonic-gate }
7277c478bd9Sstevel@tonic-gate 
7287c478bd9Sstevel@tonic-gate /*.......................................................................
7297c478bd9Sstevel@tonic-gate  * Set the prefix of subsequent history searches.
7307c478bd9Sstevel@tonic-gate  *
7317c478bd9Sstevel@tonic-gate  * Input:
7327c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
7337c478bd9Sstevel@tonic-gate  *  line  const char *  The command line who's prefix is to be used.
7347c478bd9Sstevel@tonic-gate  *  prefix_len   int    The length of the prefix.
7357c478bd9Sstevel@tonic-gate  * Output:
7367c478bd9Sstevel@tonic-gate  *  return       int    0 - OK.
7377c478bd9Sstevel@tonic-gate  *                      1 - Error.
7387c478bd9Sstevel@tonic-gate  */
_glh_search_prefix(GlHistory * glh,const char * line,int prefix_len)7397c478bd9Sstevel@tonic-gate int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len)
7407c478bd9Sstevel@tonic-gate {
7417c478bd9Sstevel@tonic-gate /*
7427c478bd9Sstevel@tonic-gate  * Check the arguments.
7437c478bd9Sstevel@tonic-gate  */
7447c478bd9Sstevel@tonic-gate   if(!glh) {
7457c478bd9Sstevel@tonic-gate     errno = EINVAL;
7467c478bd9Sstevel@tonic-gate     return 1;
7477c478bd9Sstevel@tonic-gate   };
7487c478bd9Sstevel@tonic-gate /*
7497c478bd9Sstevel@tonic-gate  * Is history enabled?
7507c478bd9Sstevel@tonic-gate  */
7517c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
7527c478bd9Sstevel@tonic-gate     return 0;
7537c478bd9Sstevel@tonic-gate /*
7547c478bd9Sstevel@tonic-gate  * Discard any existing prefix.
7557c478bd9Sstevel@tonic-gate  */
7567c478bd9Sstevel@tonic-gate   glh->prefix = _glh_discard_copy(glh, glh->prefix);
7577c478bd9Sstevel@tonic-gate /*
7587c478bd9Sstevel@tonic-gate  * Only store a copy of the prefix string if it isn't a zero-length string.
7597c478bd9Sstevel@tonic-gate  */
7607c478bd9Sstevel@tonic-gate   if(prefix_len > 0) {
7617c478bd9Sstevel@tonic-gate /*
7627c478bd9Sstevel@tonic-gate  * Get a reference-counted copy of the prefix from the history cache buffer.
7637c478bd9Sstevel@tonic-gate  */
7647c478bd9Sstevel@tonic-gate     glh->prefix = _glh_acquire_copy(glh, line, prefix_len);
7657c478bd9Sstevel@tonic-gate /*
7667c478bd9Sstevel@tonic-gate  * Was there insufficient buffer space?
7677c478bd9Sstevel@tonic-gate  */
7687c478bd9Sstevel@tonic-gate     if(!glh->prefix) {
7697c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "The search prefix is too long to store",
7707c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
7717c478bd9Sstevel@tonic-gate       errno = ENOMEM;
7727c478bd9Sstevel@tonic-gate       return 1;
7737c478bd9Sstevel@tonic-gate     };
7747c478bd9Sstevel@tonic-gate   };
7757c478bd9Sstevel@tonic-gate   return 0;
7767c478bd9Sstevel@tonic-gate }
7777c478bd9Sstevel@tonic-gate 
7787c478bd9Sstevel@tonic-gate /*.......................................................................
7797c478bd9Sstevel@tonic-gate  * Recall the oldest recorded line.
7807c478bd9Sstevel@tonic-gate  *
7817c478bd9Sstevel@tonic-gate  * Input:
7827c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
7837c478bd9Sstevel@tonic-gate  *  line      char *  The input line buffer. On input this should contain
7847c478bd9Sstevel@tonic-gate  *                    the current input line, and on output, its contents
7857c478bd9Sstevel@tonic-gate  *                    will have been replaced with the oldest line.
7867c478bd9Sstevel@tonic-gate  *  dim     size_t    The allocated dimensions of the line buffer.
7877c478bd9Sstevel@tonic-gate  * Output:
7887c478bd9Sstevel@tonic-gate  *  return    char *  A pointer to line[0], or NULL if not found.
7897c478bd9Sstevel@tonic-gate  */
_glh_oldest_line(GlHistory * glh,char * line,size_t dim)7907c478bd9Sstevel@tonic-gate char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim)
7917c478bd9Sstevel@tonic-gate {
7927c478bd9Sstevel@tonic-gate   GlhLineNode *node; /* The line location node being checked */
7937c478bd9Sstevel@tonic-gate /*
7947c478bd9Sstevel@tonic-gate  * Check the arguments.
7957c478bd9Sstevel@tonic-gate  */
7967c478bd9Sstevel@tonic-gate   if(!glh || !line) {
7977c478bd9Sstevel@tonic-gate     if(glh)
7987c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
7997c478bd9Sstevel@tonic-gate     errno = EINVAL;
8007c478bd9Sstevel@tonic-gate     return NULL;
8017c478bd9Sstevel@tonic-gate   };
8027c478bd9Sstevel@tonic-gate /*
8037c478bd9Sstevel@tonic-gate  * Is history enabled?
8047c478bd9Sstevel@tonic-gate  */
8057c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
8067c478bd9Sstevel@tonic-gate     return NULL;
8077c478bd9Sstevel@tonic-gate /*
8087c478bd9Sstevel@tonic-gate  * Check the line dimensions.
8097c478bd9Sstevel@tonic-gate  */
8107c478bd9Sstevel@tonic-gate   if(dim < strlen(line) + 1) {
8117c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
8127c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
8137c478bd9Sstevel@tonic-gate     errno = EINVAL;
8147c478bd9Sstevel@tonic-gate     return NULL;
8157c478bd9Sstevel@tonic-gate   };
8167c478bd9Sstevel@tonic-gate /*
8177c478bd9Sstevel@tonic-gate  * Preserve the input line if needed.
8187c478bd9Sstevel@tonic-gate  */
8197c478bd9Sstevel@tonic-gate   if(_glh_prepare_for_recall(glh, line))
8207c478bd9Sstevel@tonic-gate     return NULL;
8217c478bd9Sstevel@tonic-gate /*
8227c478bd9Sstevel@tonic-gate  * Locate the oldest line that belongs to the current group.
8237c478bd9Sstevel@tonic-gate  */
824*55fea89dSDan Cross   for(node=glh->list.head; node && node->group != glh->group;
8257c478bd9Sstevel@tonic-gate       node = node->next)
8267c478bd9Sstevel@tonic-gate     ;
8277c478bd9Sstevel@tonic-gate /*
8287c478bd9Sstevel@tonic-gate  * No line found?
8297c478bd9Sstevel@tonic-gate  */
8307c478bd9Sstevel@tonic-gate   if(!node)
8317c478bd9Sstevel@tonic-gate     return NULL;
8327c478bd9Sstevel@tonic-gate /*
8337c478bd9Sstevel@tonic-gate  * Record the above node as the starting point for subsequent
8347c478bd9Sstevel@tonic-gate  * searches.
8357c478bd9Sstevel@tonic-gate  */
8367c478bd9Sstevel@tonic-gate   glh->recall = node;
8377c478bd9Sstevel@tonic-gate /*
8387c478bd9Sstevel@tonic-gate  * Copy the recalled line into the provided line buffer.
8397c478bd9Sstevel@tonic-gate  */
8407c478bd9Sstevel@tonic-gate   _glh_return_line(node->line, line, dim);
8417c478bd9Sstevel@tonic-gate /*
8427c478bd9Sstevel@tonic-gate  * If we just returned the line that was being entered when the search
8437c478bd9Sstevel@tonic-gate  * session first started, cancel the search.
8447c478bd9Sstevel@tonic-gate  */
8457c478bd9Sstevel@tonic-gate   if(node == glh->list.tail)
8467c478bd9Sstevel@tonic-gate     _glh_cancel_search(glh);
8477c478bd9Sstevel@tonic-gate   return line;
8487c478bd9Sstevel@tonic-gate }
8497c478bd9Sstevel@tonic-gate 
8507c478bd9Sstevel@tonic-gate /*.......................................................................
8517c478bd9Sstevel@tonic-gate  * Recall the line that was being entered when the search started.
8527c478bd9Sstevel@tonic-gate  *
8537c478bd9Sstevel@tonic-gate  * Input:
8547c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
8557c478bd9Sstevel@tonic-gate  *  line      char *  The input line buffer. On input this should contain
8567c478bd9Sstevel@tonic-gate  *                    the current input line, and on output, its contents
8577c478bd9Sstevel@tonic-gate  *                    will have been replaced with the line that was
8587c478bd9Sstevel@tonic-gate  *                    being entered when the search was started.
8597c478bd9Sstevel@tonic-gate  *  dim     size_t    The allocated dimensions of the line buffer.
8607c478bd9Sstevel@tonic-gate  * Output:
8617c478bd9Sstevel@tonic-gate  *  return    char *  A pointer to line[0], or NULL if not found.
8627c478bd9Sstevel@tonic-gate  */
_glh_current_line(GlHistory * glh,char * line,size_t dim)8637c478bd9Sstevel@tonic-gate char *_glh_current_line(GlHistory *glh, char *line, size_t dim)
8647c478bd9Sstevel@tonic-gate {
8657c478bd9Sstevel@tonic-gate /*
8667c478bd9Sstevel@tonic-gate  * Check the arguments.
8677c478bd9Sstevel@tonic-gate  */
8687c478bd9Sstevel@tonic-gate   if(!glh || !line) {
8697c478bd9Sstevel@tonic-gate     if(glh)
8707c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
8717c478bd9Sstevel@tonic-gate     errno = EINVAL;
8727c478bd9Sstevel@tonic-gate     return NULL;
8737c478bd9Sstevel@tonic-gate   };
8747c478bd9Sstevel@tonic-gate /*
8757c478bd9Sstevel@tonic-gate  * If history isn't enabled, or no history search has yet been started,
8767c478bd9Sstevel@tonic-gate  * ignore the call.
8777c478bd9Sstevel@tonic-gate  */
8787c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall)
8797c478bd9Sstevel@tonic-gate     return NULL;
8807c478bd9Sstevel@tonic-gate /*
8817c478bd9Sstevel@tonic-gate  * Check the line dimensions.
8827c478bd9Sstevel@tonic-gate  */
8837c478bd9Sstevel@tonic-gate   if(dim < strlen(line) + 1) {
8847c478bd9Sstevel@tonic-gate     _err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
8857c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
8867c478bd9Sstevel@tonic-gate     errno = EINVAL;
8877c478bd9Sstevel@tonic-gate     return NULL;
8887c478bd9Sstevel@tonic-gate   };
8897c478bd9Sstevel@tonic-gate /*
8907c478bd9Sstevel@tonic-gate  * Copy the recalled line into the provided line buffer.
8917c478bd9Sstevel@tonic-gate  */
8927c478bd9Sstevel@tonic-gate   _glh_return_line(glh->list.tail->line, line, dim);
8937c478bd9Sstevel@tonic-gate /*
8947c478bd9Sstevel@tonic-gate  * Since we have returned to the starting point of the search, cancel it.
8957c478bd9Sstevel@tonic-gate  */
8967c478bd9Sstevel@tonic-gate   _glh_cancel_search(glh);
8977c478bd9Sstevel@tonic-gate   return line;
8987c478bd9Sstevel@tonic-gate }
8997c478bd9Sstevel@tonic-gate 
9007c478bd9Sstevel@tonic-gate /*.......................................................................
9017c478bd9Sstevel@tonic-gate  * Query the id of a history line offset by a given number of lines from
9027c478bd9Sstevel@tonic-gate  * the one that is currently being recalled. If a recall session isn't
9037c478bd9Sstevel@tonic-gate  * in progress, or the offset points outside the history list, 0 is
9047c478bd9Sstevel@tonic-gate  * returned.
9057c478bd9Sstevel@tonic-gate  *
9067c478bd9Sstevel@tonic-gate  * Input:
9077c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
9087c478bd9Sstevel@tonic-gate  *  offset       int    The line offset (0 for the current line, < 0
9097c478bd9Sstevel@tonic-gate  *                      for an older line, > 0 for a newer line.
9107c478bd9Sstevel@tonic-gate  * Output:
9117c478bd9Sstevel@tonic-gate  *  return GlhLineID    The identifier of the line that is currently
9127c478bd9Sstevel@tonic-gate  *                      being recalled, or 0 if no recall session is
9137c478bd9Sstevel@tonic-gate  *                      currently in progress.
9147c478bd9Sstevel@tonic-gate  */
_glh_line_id(GlHistory * glh,int offset)9157c478bd9Sstevel@tonic-gate GlhLineID _glh_line_id(GlHistory *glh, int offset)
9167c478bd9Sstevel@tonic-gate {
9177c478bd9Sstevel@tonic-gate   GlhLineNode *node; /* The line location node being checked */
9187c478bd9Sstevel@tonic-gate /*
9197c478bd9Sstevel@tonic-gate  * Is history enabled?
9207c478bd9Sstevel@tonic-gate  */
9217c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
9227c478bd9Sstevel@tonic-gate     return 0;
9237c478bd9Sstevel@tonic-gate /*
9247c478bd9Sstevel@tonic-gate  * Search forward 'offset' lines to find the required line.
9257c478bd9Sstevel@tonic-gate  */
9267c478bd9Sstevel@tonic-gate   if(offset >= 0) {
9277c478bd9Sstevel@tonic-gate     for(node=glh->recall; node && offset != 0; node=node->next) {
9287c478bd9Sstevel@tonic-gate       if(node->group == glh->group)
9297c478bd9Sstevel@tonic-gate 	offset--;
9307c478bd9Sstevel@tonic-gate     };
9317c478bd9Sstevel@tonic-gate   } else {
9327c478bd9Sstevel@tonic-gate     for(node=glh->recall; node && offset != 0; node=node->prev) {
9337c478bd9Sstevel@tonic-gate       if(node->group == glh->group)
9347c478bd9Sstevel@tonic-gate 	offset++;
9357c478bd9Sstevel@tonic-gate     };
9367c478bd9Sstevel@tonic-gate   };
9377c478bd9Sstevel@tonic-gate   return node ? node->id : 0;
9387c478bd9Sstevel@tonic-gate }
9397c478bd9Sstevel@tonic-gate 
9407c478bd9Sstevel@tonic-gate /*.......................................................................
9417c478bd9Sstevel@tonic-gate  * Recall a line by its history buffer ID. If the line is no longer
9427c478bd9Sstevel@tonic-gate  * in the buffer, or the id is zero, NULL is returned.
9437c478bd9Sstevel@tonic-gate  *
9447c478bd9Sstevel@tonic-gate  * Input:
9457c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
9467c478bd9Sstevel@tonic-gate  *  id   GlhLineID    The ID of the line to be returned.
9477c478bd9Sstevel@tonic-gate  *  line      char *  The input line buffer. On input this should contain
9487c478bd9Sstevel@tonic-gate  *                    the current input line, and on output, its contents
9497c478bd9Sstevel@tonic-gate  *                    will have been replaced with the saved line.
9507c478bd9Sstevel@tonic-gate  *  dim     size_t    The allocated dimensions of the line buffer.
9517c478bd9Sstevel@tonic-gate  * Output:
9527c478bd9Sstevel@tonic-gate  *  return    char *  A pointer to line[0], or NULL if not found.
9537c478bd9Sstevel@tonic-gate  */
_glh_recall_line(GlHistory * glh,GlhLineID id,char * line,size_t dim)9547c478bd9Sstevel@tonic-gate char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim)
9557c478bd9Sstevel@tonic-gate {
9567c478bd9Sstevel@tonic-gate   GlhLineNode *node; /* The line location node being checked */
9577c478bd9Sstevel@tonic-gate /*
9587c478bd9Sstevel@tonic-gate  * Is history enabled?
9597c478bd9Sstevel@tonic-gate  */
9607c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->buffer || glh->max_lines == 0)
9617c478bd9Sstevel@tonic-gate     return NULL;
9627c478bd9Sstevel@tonic-gate /*
9637c478bd9Sstevel@tonic-gate  * Preserve the input line if needed.
9647c478bd9Sstevel@tonic-gate  */
9657c478bd9Sstevel@tonic-gate   if(_glh_prepare_for_recall(glh, line))
9667c478bd9Sstevel@tonic-gate     return NULL;
9677c478bd9Sstevel@tonic-gate /*
9687c478bd9Sstevel@tonic-gate  * Search for the specified line.
9697c478bd9Sstevel@tonic-gate  */
9707c478bd9Sstevel@tonic-gate   node = _glh_find_id(glh, id);
9717c478bd9Sstevel@tonic-gate /*
9727c478bd9Sstevel@tonic-gate  * Not found?
9737c478bd9Sstevel@tonic-gate  */
9747c478bd9Sstevel@tonic-gate   if(!node || node->group != glh->group)
9757c478bd9Sstevel@tonic-gate     return NULL;
9767c478bd9Sstevel@tonic-gate /*
9777c478bd9Sstevel@tonic-gate  * Record the node of the matching line as the starting point
9787c478bd9Sstevel@tonic-gate  * for subsequent searches.
9797c478bd9Sstevel@tonic-gate  */
9807c478bd9Sstevel@tonic-gate   glh->recall = node;
9817c478bd9Sstevel@tonic-gate /*
9827c478bd9Sstevel@tonic-gate  * Copy the recalled line into the provided line buffer.
9837c478bd9Sstevel@tonic-gate  */
9847c478bd9Sstevel@tonic-gate   _glh_return_line(node->line, line, dim);
9857c478bd9Sstevel@tonic-gate   return line;
9867c478bd9Sstevel@tonic-gate }
9877c478bd9Sstevel@tonic-gate 
9887c478bd9Sstevel@tonic-gate /*.......................................................................
9897c478bd9Sstevel@tonic-gate  * Save the current history in a specified file.
9907c478bd9Sstevel@tonic-gate  *
9917c478bd9Sstevel@tonic-gate  * Input:
9927c478bd9Sstevel@tonic-gate  *  glh        GlHistory *  The input-line history maintenance object.
9937c478bd9Sstevel@tonic-gate  *  filename  const char *  The name of the new file to record the
9947c478bd9Sstevel@tonic-gate  *                          history in.
9957c478bd9Sstevel@tonic-gate  *  comment   const char *  Extra information such as timestamps will
9967c478bd9Sstevel@tonic-gate  *                          be recorded on a line started with this
9977c478bd9Sstevel@tonic-gate  *                          string, the idea being that the file can
9987c478bd9Sstevel@tonic-gate  *                          double as a command file. Specify "" if
9997c478bd9Sstevel@tonic-gate  *                          you don't care.
10007c478bd9Sstevel@tonic-gate  *  max_lines        int    The maximum number of lines to save, or -1
10017c478bd9Sstevel@tonic-gate  *                          to save all of the lines in the history
10027c478bd9Sstevel@tonic-gate  *                          list.
10037c478bd9Sstevel@tonic-gate  * Output:
10047c478bd9Sstevel@tonic-gate  *  return           int    0 - OK.
10057c478bd9Sstevel@tonic-gate  *                          1 - Error.
10067c478bd9Sstevel@tonic-gate  */
_glh_save_history(GlHistory * glh,const char * filename,const char * comment,int max_lines)10077c478bd9Sstevel@tonic-gate int _glh_save_history(GlHistory *glh, const char *filename, const char *comment,
10087c478bd9Sstevel@tonic-gate 		      int max_lines)
10097c478bd9Sstevel@tonic-gate {
10107c478bd9Sstevel@tonic-gate #ifdef WITHOUT_FILE_SYSTEM
10117c478bd9Sstevel@tonic-gate   _err_record_msg(glh->err, "Can't save history without filesystem access",
10127c478bd9Sstevel@tonic-gate 		  END_ERR_MSG);
10137c478bd9Sstevel@tonic-gate   errno = EINVAL;
10147c478bd9Sstevel@tonic-gate   return 1;
10157c478bd9Sstevel@tonic-gate #else
10167c478bd9Sstevel@tonic-gate   FILE *fp;          /* The output file */
10177c478bd9Sstevel@tonic-gate   GlhLineNode *node; /* The line being saved */
10187c478bd9Sstevel@tonic-gate   GlhLineNode *head; /* The head of the list of lines to be saved */
10197c478bd9Sstevel@tonic-gate   GlhLineSeg *seg;   /* One segment of a line being saved */
10207c478bd9Sstevel@tonic-gate /*
10217c478bd9Sstevel@tonic-gate  * Check the arguments.
10227c478bd9Sstevel@tonic-gate  */
10237c478bd9Sstevel@tonic-gate   if(!glh || !filename || !comment) {
10247c478bd9Sstevel@tonic-gate     if(glh)
10257c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
10267c478bd9Sstevel@tonic-gate     errno = EINVAL;
10277c478bd9Sstevel@tonic-gate     return 1;
10287c478bd9Sstevel@tonic-gate   };
10297c478bd9Sstevel@tonic-gate /*
10307c478bd9Sstevel@tonic-gate  * Attempt to open the specified file.
10317c478bd9Sstevel@tonic-gate  */
10327c478bd9Sstevel@tonic-gate   fp = fopen(filename, "w");
10337c478bd9Sstevel@tonic-gate   if(!fp)
10347c478bd9Sstevel@tonic-gate     return _glh_cant_save_history(glh, "Can't open", filename, NULL);
10357c478bd9Sstevel@tonic-gate /*
10367c478bd9Sstevel@tonic-gate  * If a ceiling on the number of lines to save was specified, count
10377c478bd9Sstevel@tonic-gate  * that number of lines backwards, to find the first line to be saved.
10387c478bd9Sstevel@tonic-gate  */
10397c478bd9Sstevel@tonic-gate   head = NULL;
10407c478bd9Sstevel@tonic-gate   if(max_lines >= 0) {
10417c478bd9Sstevel@tonic-gate     for(head=glh->list.tail; head && --max_lines > 0; head=head->prev)
10427c478bd9Sstevel@tonic-gate       ;
10437c478bd9Sstevel@tonic-gate   };
10447c478bd9Sstevel@tonic-gate   if(!head)
10457c478bd9Sstevel@tonic-gate     head = glh->list.head;
10467c478bd9Sstevel@tonic-gate /*
10477c478bd9Sstevel@tonic-gate  * Write the contents of the history buffer to the history file, writing
10487c478bd9Sstevel@tonic-gate  * associated data such as timestamps, to a line starting with the
10497c478bd9Sstevel@tonic-gate  * specified comment string.
10507c478bd9Sstevel@tonic-gate  */
10517c478bd9Sstevel@tonic-gate   for(node=head; node; node=node->next) {
10527c478bd9Sstevel@tonic-gate /*
10537c478bd9Sstevel@tonic-gate  * Write peripheral information associated with the line, as a comment.
10547c478bd9Sstevel@tonic-gate  */
10557c478bd9Sstevel@tonic-gate     if(fprintf(fp, "%s ", comment) < 0 ||
10567c478bd9Sstevel@tonic-gate        _glh_write_timestamp(fp, node->timestamp) ||
10577c478bd9Sstevel@tonic-gate        fprintf(fp, " %u\n", node->group) < 0) {
10587c478bd9Sstevel@tonic-gate       return _glh_cant_save_history(glh, "Error writing", filename, fp);
10597c478bd9Sstevel@tonic-gate     };
10607c478bd9Sstevel@tonic-gate /*
10617c478bd9Sstevel@tonic-gate  * Write the history line.
10627c478bd9Sstevel@tonic-gate  */
10637c478bd9Sstevel@tonic-gate     for(seg=node->line->head; seg; seg=seg->next) {
10647c478bd9Sstevel@tonic-gate       size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s);
10657c478bd9Sstevel@tonic-gate       if(fwrite(seg->s, sizeof(char), slen, fp) != slen)
10667c478bd9Sstevel@tonic-gate 	return _glh_cant_save_history(glh, "Error writing", filename, fp);
10677c478bd9Sstevel@tonic-gate     };
10687c478bd9Sstevel@tonic-gate     fputc('\n', fp);
10697c478bd9Sstevel@tonic-gate   };
10707c478bd9Sstevel@tonic-gate /*
10717c478bd9Sstevel@tonic-gate  * Close the history file.
10727c478bd9Sstevel@tonic-gate  */
10737c478bd9Sstevel@tonic-gate   if(fclose(fp) == EOF)
10747c478bd9Sstevel@tonic-gate     return _glh_cant_save_history(glh, "Error writing", filename, NULL);
10757c478bd9Sstevel@tonic-gate   return 0;
10767c478bd9Sstevel@tonic-gate #endif
10777c478bd9Sstevel@tonic-gate }
10787c478bd9Sstevel@tonic-gate 
10797c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
10807c478bd9Sstevel@tonic-gate /*.......................................................................
10817c478bd9Sstevel@tonic-gate  * This is a private error return function of _glh_save_history(). It
10827c478bd9Sstevel@tonic-gate  * composes an error report in the error buffer, composed using
10837c478bd9Sstevel@tonic-gate  * sprintf("%s %s (%s)", message, filename, strerror(errno)). It then
10847c478bd9Sstevel@tonic-gate  * closes fp and returns the error return code of _glh_save_history().
10857c478bd9Sstevel@tonic-gate  *
10867c478bd9Sstevel@tonic-gate  * Input:
10877c478bd9Sstevel@tonic-gate  *  glh        GlHistory *  The input-line history maintenance object.
10887c478bd9Sstevel@tonic-gate  *  message   const char *  A message to be followed by the filename.
10897c478bd9Sstevel@tonic-gate  *  filename  const char *  The name of the offending output file.
10907c478bd9Sstevel@tonic-gate  *  fp              FILE *  The stream to be closed (send NULL if not
10917c478bd9Sstevel@tonic-gate  *                          open).
10927c478bd9Sstevel@tonic-gate  * Output:
10937c478bd9Sstevel@tonic-gate  *  return           int    Always 1.
10947c478bd9Sstevel@tonic-gate  */
_glh_cant_save_history(GlHistory * glh,const char * message,const char * filename,FILE * fp)10957c478bd9Sstevel@tonic-gate static int _glh_cant_save_history(GlHistory *glh, const char *message,
10967c478bd9Sstevel@tonic-gate 				  const char *filename, FILE *fp)
10977c478bd9Sstevel@tonic-gate {
10987c478bd9Sstevel@tonic-gate   _err_record_msg(glh->err, message, filename, " (",
10997c478bd9Sstevel@tonic-gate 		     strerror(errno), ")", END_ERR_MSG);
11007c478bd9Sstevel@tonic-gate   if(fp)
11017c478bd9Sstevel@tonic-gate     (void) fclose(fp);
11027c478bd9Sstevel@tonic-gate   return 1;
11037c478bd9Sstevel@tonic-gate }
11047c478bd9Sstevel@tonic-gate 
11057c478bd9Sstevel@tonic-gate /*.......................................................................
11067c478bd9Sstevel@tonic-gate  * Write a timestamp to a given stdio stream, in the format
11077c478bd9Sstevel@tonic-gate  * yyyymmddhhmmss
11087c478bd9Sstevel@tonic-gate  *
11097c478bd9Sstevel@tonic-gate  * Input:
11107c478bd9Sstevel@tonic-gate  *  fp             FILE *  The stream to write to.
11117c478bd9Sstevel@tonic-gate  *  timestamp    time_t    The timestamp to be written.
11127c478bd9Sstevel@tonic-gate  * Output:
11137c478bd9Sstevel@tonic-gate  *  return          int    0 - OK.
11147c478bd9Sstevel@tonic-gate  *                         1 - Error.
11157c478bd9Sstevel@tonic-gate  */
_glh_write_timestamp(FILE * fp,time_t timestamp)11167c478bd9Sstevel@tonic-gate static int _glh_write_timestamp(FILE *fp, time_t timestamp)
11177c478bd9Sstevel@tonic-gate {
11187c478bd9Sstevel@tonic-gate   struct tm *t;  /* THe broken-down calendar time */
11197c478bd9Sstevel@tonic-gate /*
11207c478bd9Sstevel@tonic-gate  * Get the calendar components corresponding to the given timestamp.
11217c478bd9Sstevel@tonic-gate  */
11227c478bd9Sstevel@tonic-gate   if(timestamp < 0 || (t = localtime(&timestamp)) == NULL) {
11237c478bd9Sstevel@tonic-gate     if(fprintf(fp, "?") < 0)
11247c478bd9Sstevel@tonic-gate       return 1;
11257c478bd9Sstevel@tonic-gate     return 0;
11267c478bd9Sstevel@tonic-gate   };
11277c478bd9Sstevel@tonic-gate /*
11287c478bd9Sstevel@tonic-gate  * Write the calendar time as yyyymmddhhmmss.
11297c478bd9Sstevel@tonic-gate  */
11307c478bd9Sstevel@tonic-gate   if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1,
11317c478bd9Sstevel@tonic-gate 	     t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0)
11327c478bd9Sstevel@tonic-gate     return 1;
11337c478bd9Sstevel@tonic-gate   return 0;
11347c478bd9Sstevel@tonic-gate }
11357c478bd9Sstevel@tonic-gate 
11367c478bd9Sstevel@tonic-gate #endif
11377c478bd9Sstevel@tonic-gate 
11387c478bd9Sstevel@tonic-gate /*.......................................................................
11397c478bd9Sstevel@tonic-gate  * Restore previous history lines from a given file.
11407c478bd9Sstevel@tonic-gate  *
11417c478bd9Sstevel@tonic-gate  * Input:
11427c478bd9Sstevel@tonic-gate  *  glh        GlHistory *  The input-line history maintenance object.
11437c478bd9Sstevel@tonic-gate  *  filename  const char *  The name of the file to read from.
11447c478bd9Sstevel@tonic-gate  *  comment   const char *  The same comment string that was passed to
11457c478bd9Sstevel@tonic-gate  *                          _glh_save_history() when this file was
11467c478bd9Sstevel@tonic-gate  *                          written.
11477c478bd9Sstevel@tonic-gate  *  line            char *  A buffer into which lines can be read.
11487c478bd9Sstevel@tonic-gate  *  dim            size_t   The allocated dimension of line[].
11497c478bd9Sstevel@tonic-gate  * Output:
11507c478bd9Sstevel@tonic-gate  *  return           int    0 - OK.
11517c478bd9Sstevel@tonic-gate  *                          1 - Error.
11527c478bd9Sstevel@tonic-gate  */
_glh_load_history(GlHistory * glh,const char * filename,const char * comment,char * line,size_t dim)11537c478bd9Sstevel@tonic-gate int _glh_load_history(GlHistory *glh, const char *filename, const char *comment,
11547c478bd9Sstevel@tonic-gate 		      char *line, size_t dim)
11557c478bd9Sstevel@tonic-gate {
11567c478bd9Sstevel@tonic-gate #ifdef WITHOUT_FILE_SYSTEM
11577c478bd9Sstevel@tonic-gate   _err_record_msg(glh->err, "Can't load history without filesystem access",
11587c478bd9Sstevel@tonic-gate 		  END_ERR_MSG);
11597c478bd9Sstevel@tonic-gate   errno = EINVAL;
11607c478bd9Sstevel@tonic-gate   return 1;
11617c478bd9Sstevel@tonic-gate #else
11627c478bd9Sstevel@tonic-gate   FILE *fp;            /* The output file */
11637c478bd9Sstevel@tonic-gate   size_t comment_len;  /* The length of the comment string */
11647c478bd9Sstevel@tonic-gate   time_t timestamp;    /* The timestamp of the history line */
11657c478bd9Sstevel@tonic-gate   unsigned group;      /* The identifier of the history group to which */
11667c478bd9Sstevel@tonic-gate                        /*  the line belongs. */
11677c478bd9Sstevel@tonic-gate   int lineno;          /* The line number being read */
11687c478bd9Sstevel@tonic-gate /*
11697c478bd9Sstevel@tonic-gate  * Check the arguments.
11707c478bd9Sstevel@tonic-gate  */
11717c478bd9Sstevel@tonic-gate   if(!glh || !filename || !comment || !line) {
11727c478bd9Sstevel@tonic-gate     if(glh)
11737c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
11747c478bd9Sstevel@tonic-gate     errno = EINVAL;
11757c478bd9Sstevel@tonic-gate     return 1;
11767c478bd9Sstevel@tonic-gate   };
11777c478bd9Sstevel@tonic-gate /*
11787c478bd9Sstevel@tonic-gate  * Measure the length of the comment string.
11797c478bd9Sstevel@tonic-gate  */
11807c478bd9Sstevel@tonic-gate   comment_len = strlen(comment);
11817c478bd9Sstevel@tonic-gate /*
11827c478bd9Sstevel@tonic-gate  * Clear the history list.
11837c478bd9Sstevel@tonic-gate  */
11847c478bd9Sstevel@tonic-gate   _glh_clear_history(glh, 1);
11857c478bd9Sstevel@tonic-gate /*
11867c478bd9Sstevel@tonic-gate  * Attempt to open the specified file. Don't treat it as an error
11877c478bd9Sstevel@tonic-gate  * if the file doesn't exist.
11887c478bd9Sstevel@tonic-gate  */
11897c478bd9Sstevel@tonic-gate   fp = fopen(filename, "r");
11907c478bd9Sstevel@tonic-gate   if(!fp)
11917c478bd9Sstevel@tonic-gate     return 0;
11927c478bd9Sstevel@tonic-gate /*
11937c478bd9Sstevel@tonic-gate  * Attempt to read each line and preceding peripheral info, and add these
11947c478bd9Sstevel@tonic-gate  * to the history list.
11957c478bd9Sstevel@tonic-gate  */
11967c478bd9Sstevel@tonic-gate   for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) {
11977c478bd9Sstevel@tonic-gate     char *lptr;          /* A pointer into the input line */
11987c478bd9Sstevel@tonic-gate /*
11997c478bd9Sstevel@tonic-gate  * Check that the line starts with the comment string.
12007c478bd9Sstevel@tonic-gate  */
12017c478bd9Sstevel@tonic-gate     if(strncmp(line, comment, comment_len) != 0) {
12027c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno,
12037c478bd9Sstevel@tonic-gate 				    "Corrupt history parameter line", fp);
12047c478bd9Sstevel@tonic-gate     };
12057c478bd9Sstevel@tonic-gate /*
12067c478bd9Sstevel@tonic-gate  * Skip spaces and tabs after the comment.
12077c478bd9Sstevel@tonic-gate  */
12087c478bd9Sstevel@tonic-gate     for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++)
12097c478bd9Sstevel@tonic-gate       ;
12107c478bd9Sstevel@tonic-gate /*
12117c478bd9Sstevel@tonic-gate  * The next word must be a timestamp.
12127c478bd9Sstevel@tonic-gate  */
12137c478bd9Sstevel@tonic-gate     if(_glh_decode_timestamp(lptr, &lptr, &timestamp)) {
12147c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno,
12157c478bd9Sstevel@tonic-gate 				    "Corrupt timestamp", fp);
12167c478bd9Sstevel@tonic-gate     };
12177c478bd9Sstevel@tonic-gate /*
12187c478bd9Sstevel@tonic-gate  * Skip spaces and tabs.
12197c478bd9Sstevel@tonic-gate  */
12207c478bd9Sstevel@tonic-gate     while(*lptr==' ' || *lptr=='\t')
12217c478bd9Sstevel@tonic-gate       lptr++;
12227c478bd9Sstevel@tonic-gate /*
12237c478bd9Sstevel@tonic-gate  * The next word must be an unsigned integer group number.
12247c478bd9Sstevel@tonic-gate  */
12257c478bd9Sstevel@tonic-gate     group = (int) strtoul(lptr, &lptr, 10);
12267c478bd9Sstevel@tonic-gate     if(*lptr != ' ' && *lptr != '\n') {
12277c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno,
12287c478bd9Sstevel@tonic-gate 				    "Corrupt group id", fp);
12297c478bd9Sstevel@tonic-gate     };
12307c478bd9Sstevel@tonic-gate /*
12317c478bd9Sstevel@tonic-gate  * Skip spaces and tabs.
12327c478bd9Sstevel@tonic-gate  */
12337c478bd9Sstevel@tonic-gate     while(*lptr==' ' || *lptr=='\t')
12347c478bd9Sstevel@tonic-gate       lptr++;
12357c478bd9Sstevel@tonic-gate /*
12367c478bd9Sstevel@tonic-gate  * There shouldn't be anything left on the line.
12377c478bd9Sstevel@tonic-gate  */
12387c478bd9Sstevel@tonic-gate     if(*lptr != '\n') {
12397c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno,
12407c478bd9Sstevel@tonic-gate 				    "Corrupt parameter line", fp);
12417c478bd9Sstevel@tonic-gate     };
12427c478bd9Sstevel@tonic-gate /*
12437c478bd9Sstevel@tonic-gate  * Now read the history line itself.
12447c478bd9Sstevel@tonic-gate  */
12457c478bd9Sstevel@tonic-gate     lineno++;
12467c478bd9Sstevel@tonic-gate     if(fgets(line, dim, fp) == NULL)
12477c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno, "Read error", fp);
12487c478bd9Sstevel@tonic-gate /*
12497c478bd9Sstevel@tonic-gate  * Append the line to the history buffer.
12507c478bd9Sstevel@tonic-gate  */
12517c478bd9Sstevel@tonic-gate     if(_glh_add_history(glh, line, 1)) {
12527c478bd9Sstevel@tonic-gate       return _glh_cant_load_history(glh, filename, lineno,
12537c478bd9Sstevel@tonic-gate 				    "Insufficient memory to record line", fp);
12547c478bd9Sstevel@tonic-gate     };
12557c478bd9Sstevel@tonic-gate /*
12567c478bd9Sstevel@tonic-gate  * Record the group and timestamp information along with the line.
12577c478bd9Sstevel@tonic-gate  */
12587c478bd9Sstevel@tonic-gate     if(glh->list.tail) {
12597c478bd9Sstevel@tonic-gate       glh->list.tail->timestamp = timestamp;
12607c478bd9Sstevel@tonic-gate       glh->list.tail->group = group;
12617c478bd9Sstevel@tonic-gate     };
12627c478bd9Sstevel@tonic-gate   };
12637c478bd9Sstevel@tonic-gate /*
12647c478bd9Sstevel@tonic-gate  * Close the file.
12657c478bd9Sstevel@tonic-gate  */
12667c478bd9Sstevel@tonic-gate   (void) fclose(fp);
12677c478bd9Sstevel@tonic-gate   return 0;
12687c478bd9Sstevel@tonic-gate #endif
12697c478bd9Sstevel@tonic-gate }
12707c478bd9Sstevel@tonic-gate 
12717c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
12727c478bd9Sstevel@tonic-gate /*.......................................................................
12737c478bd9Sstevel@tonic-gate  * This is a private error return function of _glh_load_history().
12747c478bd9Sstevel@tonic-gate  */
_glh_cant_load_history(GlHistory * glh,const char * filename,int lineno,const char * message,FILE * fp)12757c478bd9Sstevel@tonic-gate static int _glh_cant_load_history(GlHistory *glh, const char *filename,
12767c478bd9Sstevel@tonic-gate 				  int lineno, const char *message, FILE *fp)
12777c478bd9Sstevel@tonic-gate {
12787c478bd9Sstevel@tonic-gate   char lnum[20];
12797c478bd9Sstevel@tonic-gate /*
12807c478bd9Sstevel@tonic-gate  * Convert the line number to a string.
12817c478bd9Sstevel@tonic-gate  */
12827c478bd9Sstevel@tonic-gate   snprintf(lnum, sizeof(lnum), "%d", lineno);
12837c478bd9Sstevel@tonic-gate /*
12847c478bd9Sstevel@tonic-gate  * Render an error message.
12857c478bd9Sstevel@tonic-gate  */
12867c478bd9Sstevel@tonic-gate   _err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG);
12877c478bd9Sstevel@tonic-gate /*
12887c478bd9Sstevel@tonic-gate  * Close the file.
12897c478bd9Sstevel@tonic-gate  */
12907c478bd9Sstevel@tonic-gate   if(fp)
12917c478bd9Sstevel@tonic-gate     (void) fclose(fp);
12927c478bd9Sstevel@tonic-gate   return 1;
12937c478bd9Sstevel@tonic-gate }
12947c478bd9Sstevel@tonic-gate 
12957c478bd9Sstevel@tonic-gate /*.......................................................................
12967c478bd9Sstevel@tonic-gate  * Read a timestamp from a string.
12977c478bd9Sstevel@tonic-gate  *
12987c478bd9Sstevel@tonic-gate  * Input:
12997c478bd9Sstevel@tonic-gate  *  string    char *  The string to read from.
13007c478bd9Sstevel@tonic-gate  * Input/Output:
13017c478bd9Sstevel@tonic-gate  *  endp        char ** On output *endp will point to the next unprocessed
13027c478bd9Sstevel@tonic-gate  *                      character in string[].
13037c478bd9Sstevel@tonic-gate  *  timestamp time_t *  The timestamp will be assigned to *t.
13047c478bd9Sstevel@tonic-gate  * Output:
13057c478bd9Sstevel@tonic-gate  *  return       int    0 - OK.
13067c478bd9Sstevel@tonic-gate  *                      1 - Error.
13077c478bd9Sstevel@tonic-gate  */
_glh_decode_timestamp(char * string,char ** endp,time_t * timestamp)13087c478bd9Sstevel@tonic-gate static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp)
13097c478bd9Sstevel@tonic-gate {
13107c478bd9Sstevel@tonic-gate   unsigned year,month,day,hour,min,sec;  /* Calendar time components */
13117c478bd9Sstevel@tonic-gate   struct tm t;
13127c478bd9Sstevel@tonic-gate /*
13137c478bd9Sstevel@tonic-gate  * There are 14 characters in the date format yyyymmddhhmmss.
13147c478bd9Sstevel@tonic-gate  */
1315*55fea89dSDan Cross   enum {TSLEN=14};
13167c478bd9Sstevel@tonic-gate   char timestr[TSLEN+1];   /* The timestamp part of the string */
13177c478bd9Sstevel@tonic-gate /*
13187c478bd9Sstevel@tonic-gate  * If the time wasn't available at the time that the line was recorded
13197c478bd9Sstevel@tonic-gate  * it will have been written as "?". Check for this before trying
13207c478bd9Sstevel@tonic-gate  * to read the timestamp.
13217c478bd9Sstevel@tonic-gate  */
13227c478bd9Sstevel@tonic-gate   if(string[0] == '\?') {
13237c478bd9Sstevel@tonic-gate     *endp = string+1;
13247c478bd9Sstevel@tonic-gate     *timestamp = -1;
13257c478bd9Sstevel@tonic-gate     return 0;
13267c478bd9Sstevel@tonic-gate   };
13277c478bd9Sstevel@tonic-gate /*
13287c478bd9Sstevel@tonic-gate  * The timestamp is expected to be written in the form yyyymmddhhmmss.
13297c478bd9Sstevel@tonic-gate  */
13307c478bd9Sstevel@tonic-gate   if(strlen(string) < TSLEN) {
13317c478bd9Sstevel@tonic-gate     *endp = string;
13327c478bd9Sstevel@tonic-gate     return 1;
13337c478bd9Sstevel@tonic-gate   };
13347c478bd9Sstevel@tonic-gate /*
13357c478bd9Sstevel@tonic-gate  * Copy the timestamp out of the string.
13367c478bd9Sstevel@tonic-gate  */
13377c478bd9Sstevel@tonic-gate   strncpy(timestr, string, TSLEN);
13387c478bd9Sstevel@tonic-gate   timestr[TSLEN] = '\0';
13397c478bd9Sstevel@tonic-gate /*
13407c478bd9Sstevel@tonic-gate  * Decode the timestamp.
13417c478bd9Sstevel@tonic-gate  */
13427c478bd9Sstevel@tonic-gate   if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min,
13437c478bd9Sstevel@tonic-gate 	    &sec) != 6) {
13447c478bd9Sstevel@tonic-gate     *endp = string;
13457c478bd9Sstevel@tonic-gate     return 1;
13467c478bd9Sstevel@tonic-gate   };
13477c478bd9Sstevel@tonic-gate /*
13487c478bd9Sstevel@tonic-gate  * Advance the string pointer over the successfully read timestamp.
13497c478bd9Sstevel@tonic-gate  */
13507c478bd9Sstevel@tonic-gate   *endp = string + TSLEN;
13517c478bd9Sstevel@tonic-gate /*
13527c478bd9Sstevel@tonic-gate  * Copy the read values into a struct tm.
13537c478bd9Sstevel@tonic-gate  */
13547c478bd9Sstevel@tonic-gate   t.tm_sec = sec;
13557c478bd9Sstevel@tonic-gate   t.tm_min = min;
13567c478bd9Sstevel@tonic-gate   t.tm_hour = hour;
13577c478bd9Sstevel@tonic-gate   t.tm_mday = day;
13587c478bd9Sstevel@tonic-gate   t.tm_wday = 0;
13597c478bd9Sstevel@tonic-gate   t.tm_yday = 0;
13607c478bd9Sstevel@tonic-gate   t.tm_mon = month - 1;
13617c478bd9Sstevel@tonic-gate   t.tm_year = year - 1900;
13627c478bd9Sstevel@tonic-gate   t.tm_isdst = -1;
13637c478bd9Sstevel@tonic-gate /*
13647c478bd9Sstevel@tonic-gate  * Convert the contents of the struct tm to a time_t.
13657c478bd9Sstevel@tonic-gate  */
13667c478bd9Sstevel@tonic-gate   *timestamp = mktime(&t);
13677c478bd9Sstevel@tonic-gate   return 0;
13687c478bd9Sstevel@tonic-gate }
13697c478bd9Sstevel@tonic-gate #endif
13707c478bd9Sstevel@tonic-gate 
13717c478bd9Sstevel@tonic-gate /*.......................................................................
13727c478bd9Sstevel@tonic-gate  * Switch history groups.
13737c478bd9Sstevel@tonic-gate  *
13747c478bd9Sstevel@tonic-gate  * Input:
13757c478bd9Sstevel@tonic-gate  *  glh        GlHistory *  The input-line history maintenance object.
13767c478bd9Sstevel@tonic-gate  *  group       unsigned    The new group identifier. This will be recorded
13777c478bd9Sstevel@tonic-gate  *                          with subsequent history lines, and subsequent
13787c478bd9Sstevel@tonic-gate  *                          history searches will only return lines with
13797c478bd9Sstevel@tonic-gate  *                          this group identifier. This allows multiple
13807c478bd9Sstevel@tonic-gate  *                          separate history lists to exist within
13817c478bd9Sstevel@tonic-gate  *                          a single GlHistory object. Note that the
13827c478bd9Sstevel@tonic-gate  *                          default group identifier is 0.
13837c478bd9Sstevel@tonic-gate  * Output:
13847c478bd9Sstevel@tonic-gate  *  return           int    0 - OK.
13857c478bd9Sstevel@tonic-gate  *                          1 - Error.
13867c478bd9Sstevel@tonic-gate  */
_glh_set_group(GlHistory * glh,unsigned group)13877c478bd9Sstevel@tonic-gate int _glh_set_group(GlHistory *glh, unsigned group)
13887c478bd9Sstevel@tonic-gate {
13897c478bd9Sstevel@tonic-gate /*
13907c478bd9Sstevel@tonic-gate  * Check the arguments.
13917c478bd9Sstevel@tonic-gate  */
13927c478bd9Sstevel@tonic-gate   if(!glh) {
13937c478bd9Sstevel@tonic-gate     if(glh)
13947c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
13957c478bd9Sstevel@tonic-gate     errno = EINVAL;
13967c478bd9Sstevel@tonic-gate     return 1;
13977c478bd9Sstevel@tonic-gate   };
13987c478bd9Sstevel@tonic-gate /*
13997c478bd9Sstevel@tonic-gate  * Is the group being changed?
14007c478bd9Sstevel@tonic-gate  */
14017c478bd9Sstevel@tonic-gate   if(group != glh->group) {
14027c478bd9Sstevel@tonic-gate /*
14037c478bd9Sstevel@tonic-gate  * Cancel any ongoing search.
14047c478bd9Sstevel@tonic-gate  */
14057c478bd9Sstevel@tonic-gate     if(_glh_cancel_search(glh))
14067c478bd9Sstevel@tonic-gate       return 1;
14077c478bd9Sstevel@tonic-gate /*
14087c478bd9Sstevel@tonic-gate  * Record the new group.
14097c478bd9Sstevel@tonic-gate  */
14107c478bd9Sstevel@tonic-gate     glh->group = group;
14117c478bd9Sstevel@tonic-gate   };
14127c478bd9Sstevel@tonic-gate   return 0;
14137c478bd9Sstevel@tonic-gate }
14147c478bd9Sstevel@tonic-gate 
14157c478bd9Sstevel@tonic-gate /*.......................................................................
14167c478bd9Sstevel@tonic-gate  * Query the current history group.
14177c478bd9Sstevel@tonic-gate  *
14187c478bd9Sstevel@tonic-gate  * Input:
14197c478bd9Sstevel@tonic-gate  *  glh        GlHistory *  The input-line history maintenance object.
14207c478bd9Sstevel@tonic-gate  * Output:
1421*55fea89dSDan Cross  *  return      unsigned    The group identifier.
14227c478bd9Sstevel@tonic-gate  */
_glh_get_group(GlHistory * glh)14237c478bd9Sstevel@tonic-gate int _glh_get_group(GlHistory *glh)
14247c478bd9Sstevel@tonic-gate {
14257c478bd9Sstevel@tonic-gate   return glh ? glh->group : 0;
14267c478bd9Sstevel@tonic-gate }
14277c478bd9Sstevel@tonic-gate 
14287c478bd9Sstevel@tonic-gate /*.......................................................................
14297c478bd9Sstevel@tonic-gate  * Display the contents of the history list.
14307c478bd9Sstevel@tonic-gate  *
14317c478bd9Sstevel@tonic-gate  * Input:
14327c478bd9Sstevel@tonic-gate  *  glh       GlHistory *  The input-line history maintenance object.
14337c478bd9Sstevel@tonic-gate  *  write_fn  GlWriteFn *  The function to call to write the line, or
14347c478bd9Sstevel@tonic-gate  *                         0 to discard the output.
14357c478bd9Sstevel@tonic-gate  *  data           void *  Anonymous data to pass to write_fn().
14367c478bd9Sstevel@tonic-gate  *  fmt      const char *  A format string. This can contain arbitrary
14377c478bd9Sstevel@tonic-gate  *                         characters, which are written verbatim, plus
14387c478bd9Sstevel@tonic-gate  *                         any of the following format directives:
14397c478bd9Sstevel@tonic-gate  *                          %D  -  The date, like 2001-11-20
14407c478bd9Sstevel@tonic-gate  *                          %T  -  The time of day, like 23:59:59
14417c478bd9Sstevel@tonic-gate  *                          %N  -  The sequential entry number of the
14427c478bd9Sstevel@tonic-gate  *                                 line in the history buffer.
14437c478bd9Sstevel@tonic-gate  *                          %G  -  The history group number of the line.
14447c478bd9Sstevel@tonic-gate  *                          %%  -  A literal % character.
14457c478bd9Sstevel@tonic-gate  *                          %H  -  The history line.
14467c478bd9Sstevel@tonic-gate  *  all_groups      int    If true, display history lines from all
14477c478bd9Sstevel@tonic-gate  *                         history groups. Otherwise only display
14487c478bd9Sstevel@tonic-gate  *                         those of the current history group.
14497c478bd9Sstevel@tonic-gate  *  max_lines       int    If max_lines is < 0, all available lines
14507c478bd9Sstevel@tonic-gate  *                         are displayed. Otherwise only the most
14517c478bd9Sstevel@tonic-gate  *                         recent max_lines lines will be displayed.
14527c478bd9Sstevel@tonic-gate  * Output:
14537c478bd9Sstevel@tonic-gate  *  return          int    0 - OK.
14547c478bd9Sstevel@tonic-gate  *                         1 - Error.
14557c478bd9Sstevel@tonic-gate  */
_glh_show_history(GlHistory * glh,GlWriteFn * write_fn,void * data,const char * fmt,int all_groups,int max_lines)14567c478bd9Sstevel@tonic-gate int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data,
14577c478bd9Sstevel@tonic-gate 		      const char *fmt, int all_groups, int max_lines)
14587c478bd9Sstevel@tonic-gate {
14597c478bd9Sstevel@tonic-gate   GlhLineNode *node;     /* The line being displayed */
14607c478bd9Sstevel@tonic-gate   GlhLineNode *oldest;   /* The oldest line to display */
14617c478bd9Sstevel@tonic-gate   GlhLineSeg *seg;       /* One segment of a line being displayed */
14627c478bd9Sstevel@tonic-gate   enum {TSMAX=32};       /* The maximum length of the date and time string */
14637c478bd9Sstevel@tonic-gate   char buffer[TSMAX+1];  /* The buffer in which to write the date and time */
14647c478bd9Sstevel@tonic-gate   int idlen;             /* The length of displayed ID strings */
14657c478bd9Sstevel@tonic-gate   unsigned grpmax;       /* The maximum group number in the buffer */
14667c478bd9Sstevel@tonic-gate   int grplen;            /* The number of characters needed to print grpmax */
14677c478bd9Sstevel@tonic-gate   int len;               /* The length of a string to be written */
14687c478bd9Sstevel@tonic-gate /*
14697c478bd9Sstevel@tonic-gate  * Check the arguments.
14707c478bd9Sstevel@tonic-gate  */
14717c478bd9Sstevel@tonic-gate   if(!glh || !write_fn || !fmt) {
14727c478bd9Sstevel@tonic-gate     if(glh)
14737c478bd9Sstevel@tonic-gate       _err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
14747c478bd9Sstevel@tonic-gate     errno = EINVAL;
14757c478bd9Sstevel@tonic-gate     return 1;
14767c478bd9Sstevel@tonic-gate   };
14777c478bd9Sstevel@tonic-gate /*
14787c478bd9Sstevel@tonic-gate  * Is history enabled?
14797c478bd9Sstevel@tonic-gate  */
14807c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->list.head)
14817c478bd9Sstevel@tonic-gate     return 0;
14827c478bd9Sstevel@tonic-gate /*
14837c478bd9Sstevel@tonic-gate  * Work out the length to display ID numbers, choosing the length of
1484*55fea89dSDan Cross  * the biggest number in the buffer. Smaller numbers will be padded
14857c478bd9Sstevel@tonic-gate  * with leading zeroes if needed.
14867c478bd9Sstevel@tonic-gate  */
14877c478bd9Sstevel@tonic-gate   snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) glh->list.tail->id);
14887c478bd9Sstevel@tonic-gate   idlen = strlen(buffer);
14897c478bd9Sstevel@tonic-gate /*
14907c478bd9Sstevel@tonic-gate  * Find the largest group number.
14917c478bd9Sstevel@tonic-gate  */
14927c478bd9Sstevel@tonic-gate   grpmax = 0;
14937c478bd9Sstevel@tonic-gate   for(node=glh->list.head; node; node=node->next) {
14947c478bd9Sstevel@tonic-gate     if(node->group > grpmax)
14957c478bd9Sstevel@tonic-gate       grpmax = node->group;
14967c478bd9Sstevel@tonic-gate   };
14977c478bd9Sstevel@tonic-gate /*
14987c478bd9Sstevel@tonic-gate  * Find out how many characters are needed to display the group number.
14997c478bd9Sstevel@tonic-gate  */
15007c478bd9Sstevel@tonic-gate   snprintf(buffer, sizeof(buffer), "%u", (unsigned) grpmax);
15017c478bd9Sstevel@tonic-gate   grplen = strlen(buffer);
15027c478bd9Sstevel@tonic-gate /*
15037c478bd9Sstevel@tonic-gate  * Find the node that follows the oldest line to be displayed.
15047c478bd9Sstevel@tonic-gate  */
15057c478bd9Sstevel@tonic-gate   if(max_lines < 0) {
15067c478bd9Sstevel@tonic-gate     oldest = glh->list.head;
15077c478bd9Sstevel@tonic-gate   } else if(max_lines==0) {
15087c478bd9Sstevel@tonic-gate     return 0;
15097c478bd9Sstevel@tonic-gate   } else {
15107c478bd9Sstevel@tonic-gate     for(oldest=glh->list.tail; oldest; oldest=oldest->prev) {
15117c478bd9Sstevel@tonic-gate       if((all_groups || oldest->group == glh->group) && --max_lines <= 0)
15127c478bd9Sstevel@tonic-gate 	break;
15137c478bd9Sstevel@tonic-gate     };
15147c478bd9Sstevel@tonic-gate /*
15157c478bd9Sstevel@tonic-gate  * If the number of lines in the buffer doesn't exceed the specified
15167c478bd9Sstevel@tonic-gate  * maximum, start from the oldest line in the buffer.
15177c478bd9Sstevel@tonic-gate  */
15187c478bd9Sstevel@tonic-gate     if(!oldest)
15197c478bd9Sstevel@tonic-gate       oldest = glh->list.head;
15207c478bd9Sstevel@tonic-gate   };
15217c478bd9Sstevel@tonic-gate /*
15227c478bd9Sstevel@tonic-gate  * List the history lines in increasing time order.
15237c478bd9Sstevel@tonic-gate  */
15247c478bd9Sstevel@tonic-gate   for(node=oldest; node; node=node->next) {
15257c478bd9Sstevel@tonic-gate /*
15267c478bd9Sstevel@tonic-gate  * Only display lines from the current history group, unless
15277c478bd9Sstevel@tonic-gate  * told otherwise.
15287c478bd9Sstevel@tonic-gate  */
15297c478bd9Sstevel@tonic-gate     if(all_groups || node->group == glh->group) {
15307c478bd9Sstevel@tonic-gate       const char *fptr;      /* A pointer into the format string */
15317c478bd9Sstevel@tonic-gate       struct tm *t = NULL;   /* The broken time version of the timestamp */
15327c478bd9Sstevel@tonic-gate /*
15337c478bd9Sstevel@tonic-gate  * Work out the calendar representation of the node timestamp.
15347c478bd9Sstevel@tonic-gate  */
15357c478bd9Sstevel@tonic-gate       if(node->timestamp != (time_t) -1)
15367c478bd9Sstevel@tonic-gate 	t = localtime(&node->timestamp);
15377c478bd9Sstevel@tonic-gate /*
15387c478bd9Sstevel@tonic-gate  * Parse the format string.
15397c478bd9Sstevel@tonic-gate  */
15407c478bd9Sstevel@tonic-gate       fptr = fmt;
15417c478bd9Sstevel@tonic-gate       while(*fptr) {
15427c478bd9Sstevel@tonic-gate /*
15437c478bd9Sstevel@tonic-gate  * Search for the start of the next format directive or the end of the string.
15447c478bd9Sstevel@tonic-gate  */
15457c478bd9Sstevel@tonic-gate 	const char *start = fptr;
15467c478bd9Sstevel@tonic-gate 	while(*fptr && *fptr != '%')
15477c478bd9Sstevel@tonic-gate 	  fptr++;
15487c478bd9Sstevel@tonic-gate /*
15497c478bd9Sstevel@tonic-gate  * Display any literal characters that precede the located directive.
15507c478bd9Sstevel@tonic-gate  */
15517c478bd9Sstevel@tonic-gate 	if(fptr > start) {
15527c478bd9Sstevel@tonic-gate 	  len = (int) (fptr - start);
15537c478bd9Sstevel@tonic-gate 	  if(write_fn(data, start, len) != len)
15547c478bd9Sstevel@tonic-gate 	    return 1;
15557c478bd9Sstevel@tonic-gate 	};
15567c478bd9Sstevel@tonic-gate /*
15577c478bd9Sstevel@tonic-gate  * Did we hit a new directive before the end of the line?
15587c478bd9Sstevel@tonic-gate  */
15597c478bd9Sstevel@tonic-gate 	if(*fptr) {
15607c478bd9Sstevel@tonic-gate /*
15617c478bd9Sstevel@tonic-gate  * Obey the directive. Ignore unknown directives.
15627c478bd9Sstevel@tonic-gate  */
15637c478bd9Sstevel@tonic-gate 	  switch(*++fptr) {
15647c478bd9Sstevel@tonic-gate 	  case 'D':          /* Display the date */
15657c478bd9Sstevel@tonic-gate 	    if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) {
15667c478bd9Sstevel@tonic-gate 	      len = strlen(buffer);
15677c478bd9Sstevel@tonic-gate 	      if(write_fn(data, buffer, len) != len)
15687c478bd9Sstevel@tonic-gate 		return 1;
15697c478bd9Sstevel@tonic-gate 	    };
15707c478bd9Sstevel@tonic-gate 	    break;
15717c478bd9Sstevel@tonic-gate 	  case 'T':          /* Display the time of day */
15727c478bd9Sstevel@tonic-gate 	    if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) {
15737c478bd9Sstevel@tonic-gate 	      len = strlen(buffer);
15747c478bd9Sstevel@tonic-gate 	      if(write_fn(data, buffer, len) != len)
15757c478bd9Sstevel@tonic-gate 		return 1;
15767c478bd9Sstevel@tonic-gate 	    };
15777c478bd9Sstevel@tonic-gate 	    break;
15787c478bd9Sstevel@tonic-gate 	  case 'N':          /* Display the sequential entry number */
15797c478bd9Sstevel@tonic-gate 	    snprintf(buffer, sizeof(buffer), "%*lu", idlen, (unsigned long) node->id);
15807c478bd9Sstevel@tonic-gate 	    len = strlen(buffer);
15817c478bd9Sstevel@tonic-gate 	    if(write_fn(data, buffer, len) != len)
15827c478bd9Sstevel@tonic-gate 	      return 1;
15837c478bd9Sstevel@tonic-gate 	    break;
15847c478bd9Sstevel@tonic-gate 	  case 'G':
15857c478bd9Sstevel@tonic-gate 	    snprintf(buffer, sizeof(buffer), "%*u", grplen, (unsigned) node->group);
15867c478bd9Sstevel@tonic-gate 	    len = strlen(buffer);
15877c478bd9Sstevel@tonic-gate 	    if(write_fn(data, buffer, len) != len)
15887c478bd9Sstevel@tonic-gate 	      return 1;
15897c478bd9Sstevel@tonic-gate 	    break;
15907c478bd9Sstevel@tonic-gate 	  case 'H':          /* Display the history line */
15917c478bd9Sstevel@tonic-gate 	    for(seg=node->line->head; seg; seg=seg->next) {
15927c478bd9Sstevel@tonic-gate 	      len = seg->next ? GLH_SEG_SIZE : strlen(seg->s);
15937c478bd9Sstevel@tonic-gate 	      if(write_fn(data, seg->s, len) != len)
15947c478bd9Sstevel@tonic-gate 		return 1;
15957c478bd9Sstevel@tonic-gate 	    };
15967c478bd9Sstevel@tonic-gate 	    break;
15977c478bd9Sstevel@tonic-gate 	  case '%':          /* A literal % symbol */
15987c478bd9Sstevel@tonic-gate 	    if(write_fn(data, "%", 1) != 1)
15997c478bd9Sstevel@tonic-gate 	      return 1;
16007c478bd9Sstevel@tonic-gate 	    break;
16017c478bd9Sstevel@tonic-gate 	  };
16027c478bd9Sstevel@tonic-gate /*
16037c478bd9Sstevel@tonic-gate  * Skip the directive.
16047c478bd9Sstevel@tonic-gate  */
16057c478bd9Sstevel@tonic-gate 	  if(*fptr)
16067c478bd9Sstevel@tonic-gate 	    fptr++;
16077c478bd9Sstevel@tonic-gate 	};
16087c478bd9Sstevel@tonic-gate       };
16097c478bd9Sstevel@tonic-gate     };
16107c478bd9Sstevel@tonic-gate   };
16117c478bd9Sstevel@tonic-gate   return 0;
16127c478bd9Sstevel@tonic-gate }
16137c478bd9Sstevel@tonic-gate 
16147c478bd9Sstevel@tonic-gate /*.......................................................................
16157c478bd9Sstevel@tonic-gate  * Change the size of the history buffer.
16167c478bd9Sstevel@tonic-gate  *
16177c478bd9Sstevel@tonic-gate  * Input:
16187c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
16197c478bd9Sstevel@tonic-gate  *  bufsize   size_t    The number of bytes in the history buffer, or 0
16207c478bd9Sstevel@tonic-gate  *                      to delete the buffer completely.
16217c478bd9Sstevel@tonic-gate  * Output:
16227c478bd9Sstevel@tonic-gate  *  return       int    0 - OK.
16237c478bd9Sstevel@tonic-gate  *                      1 - Insufficient memory (the previous buffer
16247c478bd9Sstevel@tonic-gate  *                          will have been retained). No error message
16257c478bd9Sstevel@tonic-gate  *                          will be displayed.
16267c478bd9Sstevel@tonic-gate  */
_glh_resize_history(GlHistory * glh,size_t bufsize)16277c478bd9Sstevel@tonic-gate int _glh_resize_history(GlHistory *glh, size_t bufsize)
16287c478bd9Sstevel@tonic-gate {
16297c478bd9Sstevel@tonic-gate   int nbuff;     /* The number of segments in the new buffer */
16307c478bd9Sstevel@tonic-gate   int i;
16317c478bd9Sstevel@tonic-gate /*
16327c478bd9Sstevel@tonic-gate  * Check the arguments.
16337c478bd9Sstevel@tonic-gate  */
16347c478bd9Sstevel@tonic-gate   if(!glh) {
16357c478bd9Sstevel@tonic-gate     errno = EINVAL;
16367c478bd9Sstevel@tonic-gate     return 1;
16377c478bd9Sstevel@tonic-gate   };
16387c478bd9Sstevel@tonic-gate /*
16397c478bd9Sstevel@tonic-gate  * How many buffer segments does the requested buffer size correspond
16407c478bd9Sstevel@tonic-gate  * to?
16417c478bd9Sstevel@tonic-gate  */
16427c478bd9Sstevel@tonic-gate   nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE;
16437c478bd9Sstevel@tonic-gate /*
16447c478bd9Sstevel@tonic-gate  * Has a different size than the current size been requested?
16457c478bd9Sstevel@tonic-gate  */
16467c478bd9Sstevel@tonic-gate   if(glh->nbuff != nbuff) {
16477c478bd9Sstevel@tonic-gate /*
16487c478bd9Sstevel@tonic-gate  * Cancel any ongoing search.
16497c478bd9Sstevel@tonic-gate  */
16507c478bd9Sstevel@tonic-gate     (void) _glh_cancel_search(glh);
16517c478bd9Sstevel@tonic-gate /*
16527c478bd9Sstevel@tonic-gate  * Create a wholly new buffer?
16537c478bd9Sstevel@tonic-gate  */
16547c478bd9Sstevel@tonic-gate     if(glh->nbuff == 0 && nbuff>0) {
16557c478bd9Sstevel@tonic-gate       glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff);
16567c478bd9Sstevel@tonic-gate       if(!glh->buffer)
16577c478bd9Sstevel@tonic-gate 	return 1;
16587c478bd9Sstevel@tonic-gate       glh->nbuff = nbuff;
16597c478bd9Sstevel@tonic-gate       glh->nfree = glh->nbuff;
16607c478bd9Sstevel@tonic-gate       glh->nbusy = 0;
16617c478bd9Sstevel@tonic-gate       glh->nline = 0;
16627c478bd9Sstevel@tonic-gate /*
16637c478bd9Sstevel@tonic-gate  * Link the currently unused nodes of the buffer into a list.
16647c478bd9Sstevel@tonic-gate  */
16657c478bd9Sstevel@tonic-gate       glh->unused = glh->buffer;
16667c478bd9Sstevel@tonic-gate       for(i=0; i<glh->nbuff-1; i++) {
16677c478bd9Sstevel@tonic-gate 	GlhLineSeg *seg = glh->unused + i;
16687c478bd9Sstevel@tonic-gate 	seg->next = seg + 1;
16697c478bd9Sstevel@tonic-gate       };
16707c478bd9Sstevel@tonic-gate       glh->unused[i].next = NULL;
16717c478bd9Sstevel@tonic-gate /*
16727c478bd9Sstevel@tonic-gate  * Delete an existing buffer?
16737c478bd9Sstevel@tonic-gate  */
16747c478bd9Sstevel@tonic-gate     } else if(nbuff == 0) {
16757c478bd9Sstevel@tonic-gate       _glh_clear_history(glh, 1);
16767c478bd9Sstevel@tonic-gate       free(glh->buffer);
16777c478bd9Sstevel@tonic-gate       glh->buffer = NULL;
16787c478bd9Sstevel@tonic-gate       glh->unused = NULL;
16797c478bd9Sstevel@tonic-gate       glh->nbuff = 0;
16807c478bd9Sstevel@tonic-gate       glh->nfree = 0;
16817c478bd9Sstevel@tonic-gate       glh->nbusy = 0;
16827c478bd9Sstevel@tonic-gate       glh->nline = 0;
16837c478bd9Sstevel@tonic-gate /*
16847c478bd9Sstevel@tonic-gate  * Change from one finite buffer size to another?
16857c478bd9Sstevel@tonic-gate  */
16867c478bd9Sstevel@tonic-gate     } else {
16877c478bd9Sstevel@tonic-gate       GlhLineSeg *buffer; /* The resized buffer */
16887c478bd9Sstevel@tonic-gate       int nbusy;      /* The number of used line segments in the new buffer */
16897c478bd9Sstevel@tonic-gate /*
16907c478bd9Sstevel@tonic-gate  * Starting from the oldest line in the buffer, discard lines until
16917c478bd9Sstevel@tonic-gate  * the buffer contains at most 'nbuff' used line segments.
16927c478bd9Sstevel@tonic-gate  */
16937c478bd9Sstevel@tonic-gate       while(glh->list.head && glh->nbusy > nbuff)
16947c478bd9Sstevel@tonic-gate 	_glh_discard_line(glh, glh->list.head);
16957c478bd9Sstevel@tonic-gate /*
16967c478bd9Sstevel@tonic-gate  * Attempt to allocate a new buffer.
16977c478bd9Sstevel@tonic-gate  */
16987c478bd9Sstevel@tonic-gate       buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg));
16997c478bd9Sstevel@tonic-gate       if(!buffer) {
17007c478bd9Sstevel@tonic-gate 	errno = ENOMEM;
17017c478bd9Sstevel@tonic-gate 	return 1;
17027c478bd9Sstevel@tonic-gate       };
17037c478bd9Sstevel@tonic-gate /*
17047c478bd9Sstevel@tonic-gate  * Copy the used segments of the old buffer to the start of the new buffer.
17057c478bd9Sstevel@tonic-gate  */
17067c478bd9Sstevel@tonic-gate       nbusy = 0;
17077c478bd9Sstevel@tonic-gate       for(i=0; i<GLH_HASH_SIZE; i++) {
17087c478bd9Sstevel@tonic-gate 	GlhHashBucket *b = glh->hash.bucket + i;
17097c478bd9Sstevel@tonic-gate 	GlhHashNode *hnode;
17107c478bd9Sstevel@tonic-gate 	for(hnode=b->lines; hnode; hnode=hnode->next) {
17117c478bd9Sstevel@tonic-gate 	  GlhLineSeg *seg = hnode->head;
17127c478bd9Sstevel@tonic-gate 	  hnode->head = buffer + nbusy;
17137c478bd9Sstevel@tonic-gate 	  for( ; seg; seg=seg->next) {
17147c478bd9Sstevel@tonic-gate 	    buffer[nbusy] = *seg;
17157c478bd9Sstevel@tonic-gate 	    buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL;
17167c478bd9Sstevel@tonic-gate 	    nbusy++;
17177c478bd9Sstevel@tonic-gate 	  };
17187c478bd9Sstevel@tonic-gate 	};
17197c478bd9Sstevel@tonic-gate       };
17207c478bd9Sstevel@tonic-gate /*
17217c478bd9Sstevel@tonic-gate  * Make a list of the new buffer's unused segments.
17227c478bd9Sstevel@tonic-gate  */
17237c478bd9Sstevel@tonic-gate       for(i=nbusy; i<nbuff-1; i++)
17247c478bd9Sstevel@tonic-gate 	buffer[i].next = &buffer[i+1];
17257c478bd9Sstevel@tonic-gate       if(i < nbuff)
17267c478bd9Sstevel@tonic-gate 	buffer[i].next = NULL;
17277c478bd9Sstevel@tonic-gate /*
17287c478bd9Sstevel@tonic-gate  * Discard the old buffer.
17297c478bd9Sstevel@tonic-gate  */
17307c478bd9Sstevel@tonic-gate       free(glh->buffer);
17317c478bd9Sstevel@tonic-gate /*
17327c478bd9Sstevel@tonic-gate  * Install the new buffer.
17337c478bd9Sstevel@tonic-gate  */
17347c478bd9Sstevel@tonic-gate       glh->buffer = buffer;
17357c478bd9Sstevel@tonic-gate       glh->nbuff = nbuff;
17367c478bd9Sstevel@tonic-gate       glh->nbusy = nbusy;
17377c478bd9Sstevel@tonic-gate       glh->nfree = nbuff - nbusy;
17387c478bd9Sstevel@tonic-gate       glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL;
17397c478bd9Sstevel@tonic-gate     };
17407c478bd9Sstevel@tonic-gate   };
17417c478bd9Sstevel@tonic-gate   return 0;
17427c478bd9Sstevel@tonic-gate }
17437c478bd9Sstevel@tonic-gate 
17447c478bd9Sstevel@tonic-gate /*.......................................................................
17457c478bd9Sstevel@tonic-gate  * Set an upper limit to the number of lines that can be recorded in the
17467c478bd9Sstevel@tonic-gate  * history list, or remove a previously specified limit.
17477c478bd9Sstevel@tonic-gate  *
17487c478bd9Sstevel@tonic-gate  * Input:
17497c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
17507c478bd9Sstevel@tonic-gate  *  max_lines    int    The maximum number of lines to allow, or -1 to
17517c478bd9Sstevel@tonic-gate  *                      cancel a previous limit and allow as many lines
17527c478bd9Sstevel@tonic-gate  *                      as will fit in the current history buffer size.
17537c478bd9Sstevel@tonic-gate  */
_glh_limit_history(GlHistory * glh,int max_lines)17547c478bd9Sstevel@tonic-gate void _glh_limit_history(GlHistory *glh, int max_lines)
17557c478bd9Sstevel@tonic-gate {
17567c478bd9Sstevel@tonic-gate   if(!glh)
17577c478bd9Sstevel@tonic-gate     return;
17587c478bd9Sstevel@tonic-gate /*
17597c478bd9Sstevel@tonic-gate  * Apply a new limit?
17607c478bd9Sstevel@tonic-gate  */
17617c478bd9Sstevel@tonic-gate   if(max_lines >= 0 && max_lines != glh->max_lines) {
17627c478bd9Sstevel@tonic-gate /*
17637c478bd9Sstevel@tonic-gate  * Count successively older lines until we reach the start of the
17647c478bd9Sstevel@tonic-gate  * list, or until we have seen max_lines lines (at which point 'node'
17657c478bd9Sstevel@tonic-gate  * will be line number max_lines+1).
17667c478bd9Sstevel@tonic-gate  */
17677c478bd9Sstevel@tonic-gate     int nline = 0;
17687c478bd9Sstevel@tonic-gate     GlhLineNode *node;
17697c478bd9Sstevel@tonic-gate     for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev)
17707c478bd9Sstevel@tonic-gate       ;
17717c478bd9Sstevel@tonic-gate /*
17727c478bd9Sstevel@tonic-gate  * Discard any lines that exceed the limit.
17737c478bd9Sstevel@tonic-gate  */
17747c478bd9Sstevel@tonic-gate     if(node) {
17757c478bd9Sstevel@tonic-gate       GlhLineNode *oldest = node->next;  /* The oldest line to be kept */
17767c478bd9Sstevel@tonic-gate /*
17777c478bd9Sstevel@tonic-gate  * Delete nodes from the head of the list until we reach the node that
17787c478bd9Sstevel@tonic-gate  * is to be kept.
17797c478bd9Sstevel@tonic-gate  */
17807c478bd9Sstevel@tonic-gate       while(glh->list.head && glh->list.head != oldest)
17817c478bd9Sstevel@tonic-gate 	_glh_discard_line(glh, glh->list.head);
17827c478bd9Sstevel@tonic-gate     };
17837c478bd9Sstevel@tonic-gate   };
17847c478bd9Sstevel@tonic-gate /*
17857c478bd9Sstevel@tonic-gate  * Record the new limit.
17867c478bd9Sstevel@tonic-gate  */
17877c478bd9Sstevel@tonic-gate   glh->max_lines = max_lines;
17887c478bd9Sstevel@tonic-gate   return;
17897c478bd9Sstevel@tonic-gate }
17907c478bd9Sstevel@tonic-gate 
17917c478bd9Sstevel@tonic-gate /*.......................................................................
17927c478bd9Sstevel@tonic-gate  * Discard either all history, or the history associated with the current
17937c478bd9Sstevel@tonic-gate  * history group.
17947c478bd9Sstevel@tonic-gate  *
17957c478bd9Sstevel@tonic-gate  * Input:
17967c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
17977c478bd9Sstevel@tonic-gate  *  all_groups   int    If true, clear all of the history. If false,
17987c478bd9Sstevel@tonic-gate  *                      clear only the stored lines associated with the
17997c478bd9Sstevel@tonic-gate  *                      currently selected history group.
18007c478bd9Sstevel@tonic-gate  */
_glh_clear_history(GlHistory * glh,int all_groups)18017c478bd9Sstevel@tonic-gate void _glh_clear_history(GlHistory *glh, int all_groups)
18027c478bd9Sstevel@tonic-gate {
18037c478bd9Sstevel@tonic-gate   int i;
18047c478bd9Sstevel@tonic-gate /*
18057c478bd9Sstevel@tonic-gate  * Check the arguments.
18067c478bd9Sstevel@tonic-gate  */
18077c478bd9Sstevel@tonic-gate   if(!glh)
18087c478bd9Sstevel@tonic-gate     return;
18097c478bd9Sstevel@tonic-gate /*
18107c478bd9Sstevel@tonic-gate  * Cancel any ongoing search.
18117c478bd9Sstevel@tonic-gate  */
18127c478bd9Sstevel@tonic-gate   (void) _glh_cancel_search(glh);
18137c478bd9Sstevel@tonic-gate /*
18147c478bd9Sstevel@tonic-gate  * Delete all history lines regardless of group?
18157c478bd9Sstevel@tonic-gate  */
18167c478bd9Sstevel@tonic-gate   if(all_groups) {
18177c478bd9Sstevel@tonic-gate /*
18187c478bd9Sstevel@tonic-gate  * Claer the time-ordered list of lines.
18197c478bd9Sstevel@tonic-gate  */
18207c478bd9Sstevel@tonic-gate     _rst_FreeList(glh->list.node_mem);
18217c478bd9Sstevel@tonic-gate     glh->list.head = glh->list.tail = NULL;
18227c478bd9Sstevel@tonic-gate     glh->nline = 0;
18237c478bd9Sstevel@tonic-gate     glh->id_node = NULL;
18247c478bd9Sstevel@tonic-gate /*
18257c478bd9Sstevel@tonic-gate  * Clear the hash table.
18267c478bd9Sstevel@tonic-gate  */
18277c478bd9Sstevel@tonic-gate     for(i=0; i<GLH_HASH_SIZE; i++)
18287c478bd9Sstevel@tonic-gate       glh->hash.bucket[i].lines = NULL;
18297c478bd9Sstevel@tonic-gate     _rst_FreeList(glh->hash.node_mem);
18307c478bd9Sstevel@tonic-gate /*
18317c478bd9Sstevel@tonic-gate  * Move all line segment nodes back onto the list of unused segments.
18327c478bd9Sstevel@tonic-gate  */
18337c478bd9Sstevel@tonic-gate     if(glh->buffer) {
18347c478bd9Sstevel@tonic-gate       glh->unused = glh->buffer;
18357c478bd9Sstevel@tonic-gate       for(i=0; i<glh->nbuff-1; i++) {
18367c478bd9Sstevel@tonic-gate 	GlhLineSeg *seg = glh->unused + i;
18377c478bd9Sstevel@tonic-gate 	seg->next = seg + 1;
18387c478bd9Sstevel@tonic-gate       };
18397c478bd9Sstevel@tonic-gate       glh->unused[i].next = NULL;
18407c478bd9Sstevel@tonic-gate       glh->nfree = glh->nbuff;
18417c478bd9Sstevel@tonic-gate       glh->nbusy = 0;
18427c478bd9Sstevel@tonic-gate     } else {
18437c478bd9Sstevel@tonic-gate       glh->unused = NULL;
18447c478bd9Sstevel@tonic-gate       glh->nbusy = glh->nfree = 0;
18457c478bd9Sstevel@tonic-gate     };
18467c478bd9Sstevel@tonic-gate /*
18477c478bd9Sstevel@tonic-gate  * Just delete lines of the current group?
18487c478bd9Sstevel@tonic-gate  */
18497c478bd9Sstevel@tonic-gate   } else {
18507c478bd9Sstevel@tonic-gate     GlhLineNode *node;  /* The line node being checked */
18517c478bd9Sstevel@tonic-gate     GlhLineNode *next;  /* The line node that follows 'node' */
18527c478bd9Sstevel@tonic-gate /*
18537c478bd9Sstevel@tonic-gate  * Search out and delete the line nodes of the current group.
18547c478bd9Sstevel@tonic-gate  */
18557c478bd9Sstevel@tonic-gate     for(node=glh->list.head; node; node=next) {
18567c478bd9Sstevel@tonic-gate /*
18577c478bd9Sstevel@tonic-gate  * Keep a record of the following node before we delete the current
18587c478bd9Sstevel@tonic-gate  * node.
18597c478bd9Sstevel@tonic-gate  */
18607c478bd9Sstevel@tonic-gate       next = node->next;
18617c478bd9Sstevel@tonic-gate /*
18627c478bd9Sstevel@tonic-gate  * Discard this node?
18637c478bd9Sstevel@tonic-gate  */
18647c478bd9Sstevel@tonic-gate       if(node->group == glh->group)
18657c478bd9Sstevel@tonic-gate 	_glh_discard_line(glh, node);
18667c478bd9Sstevel@tonic-gate     };
18677c478bd9Sstevel@tonic-gate   };
18687c478bd9Sstevel@tonic-gate   return;
18697c478bd9Sstevel@tonic-gate }
18707c478bd9Sstevel@tonic-gate 
18717c478bd9Sstevel@tonic-gate /*.......................................................................
18727c478bd9Sstevel@tonic-gate  * Temporarily enable or disable the history list.
18737c478bd9Sstevel@tonic-gate  *
18747c478bd9Sstevel@tonic-gate  * Input:
18757c478bd9Sstevel@tonic-gate  *  glh    GlHistory *  The input-line history maintenance object.
18767c478bd9Sstevel@tonic-gate  *  enable       int    If true, turn on the history mechanism. If
18777c478bd9Sstevel@tonic-gate  *                      false, disable it.
18787c478bd9Sstevel@tonic-gate  */
_glh_toggle_history(GlHistory * glh,int enable)18797c478bd9Sstevel@tonic-gate void _glh_toggle_history(GlHistory *glh, int enable)
18807c478bd9Sstevel@tonic-gate {
18817c478bd9Sstevel@tonic-gate   if(glh)
18827c478bd9Sstevel@tonic-gate     glh->enable = enable;
18837c478bd9Sstevel@tonic-gate }
18847c478bd9Sstevel@tonic-gate 
18857c478bd9Sstevel@tonic-gate /*.......................................................................
18867c478bd9Sstevel@tonic-gate  * Discard a given archived input line.
18877c478bd9Sstevel@tonic-gate  *
18887c478bd9Sstevel@tonic-gate  * Input:
18897c478bd9Sstevel@tonic-gate  *  glh      GlHistory *  The history container object.
18907c478bd9Sstevel@tonic-gate  *  node   GlhLineNode *  The line to be discarded, specified via its
18917c478bd9Sstevel@tonic-gate  *                        entry in the time-ordered list of historical
18927c478bd9Sstevel@tonic-gate  *                        input lines.
18937c478bd9Sstevel@tonic-gate  */
_glh_discard_line(GlHistory * glh,GlhLineNode * node)18947c478bd9Sstevel@tonic-gate static void _glh_discard_line(GlHistory *glh, GlhLineNode *node)
18957c478bd9Sstevel@tonic-gate {
18967c478bd9Sstevel@tonic-gate /*
18977c478bd9Sstevel@tonic-gate  * Remove the node from the linked list.
18987c478bd9Sstevel@tonic-gate  */
18997c478bd9Sstevel@tonic-gate   if(node->prev)
19007c478bd9Sstevel@tonic-gate     node->prev->next = node->next;
19017c478bd9Sstevel@tonic-gate   else
19027c478bd9Sstevel@tonic-gate     glh->list.head = node->next;
19037c478bd9Sstevel@tonic-gate   if(node->next)
19047c478bd9Sstevel@tonic-gate     node->next->prev = node->prev;
19057c478bd9Sstevel@tonic-gate   else
19067c478bd9Sstevel@tonic-gate     glh->list.tail = node->prev;
19077c478bd9Sstevel@tonic-gate /*
19087c478bd9Sstevel@tonic-gate  * If we are deleting the node that is marked as the start point of the
19097c478bd9Sstevel@tonic-gate  * last ID search, remove the cached starting point.
19107c478bd9Sstevel@tonic-gate  */
19117c478bd9Sstevel@tonic-gate   if(node == glh->id_node)
19127c478bd9Sstevel@tonic-gate     glh->id_node = NULL;
19137c478bd9Sstevel@tonic-gate /*
19147c478bd9Sstevel@tonic-gate  * If we are deleting the node that is marked as the start point of the
19157c478bd9Sstevel@tonic-gate  * next prefix search, cancel the search.
19167c478bd9Sstevel@tonic-gate  */
19177c478bd9Sstevel@tonic-gate   if(node == glh->recall)
19187c478bd9Sstevel@tonic-gate     _glh_cancel_search(glh);
19197c478bd9Sstevel@tonic-gate /*
19207c478bd9Sstevel@tonic-gate  * Delete our copy of the line.
19217c478bd9Sstevel@tonic-gate  */
19227c478bd9Sstevel@tonic-gate   node->line = _glh_discard_copy(glh, node->line);
19237c478bd9Sstevel@tonic-gate /*
19247c478bd9Sstevel@tonic-gate  * Return the node to the freelist.
19257c478bd9Sstevel@tonic-gate  */
19267c478bd9Sstevel@tonic-gate   (void) _del_FreeListNode(glh->list.node_mem, node);
19277c478bd9Sstevel@tonic-gate /*
19287c478bd9Sstevel@tonic-gate  * Record the removal of a line from the list.
19297c478bd9Sstevel@tonic-gate  */
19307c478bd9Sstevel@tonic-gate   glh->nline--;
19317c478bd9Sstevel@tonic-gate   return;
19327c478bd9Sstevel@tonic-gate }
19337c478bd9Sstevel@tonic-gate 
19347c478bd9Sstevel@tonic-gate /*.......................................................................
19357c478bd9Sstevel@tonic-gate  * Lookup the details of a given history line, given its id.
19367c478bd9Sstevel@tonic-gate  *
19377c478bd9Sstevel@tonic-gate  * Input:
19387c478bd9Sstevel@tonic-gate  *  glh      GlHistory *  The input-line history maintenance object.
19397c478bd9Sstevel@tonic-gate  *  id        GlLineID    The sequential number of the line.
19407c478bd9Sstevel@tonic-gate  * Input/Output:
19417c478bd9Sstevel@tonic-gate  *  line    const char ** A pointer to a copy of the history line will be
19427c478bd9Sstevel@tonic-gate  *                        assigned to *line. Beware that this pointer may
19437c478bd9Sstevel@tonic-gate  *                        be invalidated by the next call to any public
19447c478bd9Sstevel@tonic-gate  *                        history function.
19457c478bd9Sstevel@tonic-gate  *  group     unsigned *  The group membership of the line will be assigned
19467c478bd9Sstevel@tonic-gate  *                        to *group.
19477c478bd9Sstevel@tonic-gate  *  timestamp   time_t *  The timestamp of the line will be assigned to
19487c478bd9Sstevel@tonic-gate  *                        *timestamp.
19497c478bd9Sstevel@tonic-gate  * Output:
19507c478bd9Sstevel@tonic-gate  *  return         int    0 - The requested line wasn't found.
19517c478bd9Sstevel@tonic-gate  *                        1 - The line was found.
19527c478bd9Sstevel@tonic-gate  */
_glh_lookup_history(GlHistory * glh,GlhLineID id,const char ** line,unsigned * group,time_t * timestamp)19537c478bd9Sstevel@tonic-gate int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line,
19547c478bd9Sstevel@tonic-gate 			unsigned *group, time_t *timestamp)
19557c478bd9Sstevel@tonic-gate {
19567c478bd9Sstevel@tonic-gate   GlhLineNode *node; /* The located line location node */
19577c478bd9Sstevel@tonic-gate /*
19587c478bd9Sstevel@tonic-gate  * Check the arguments.
19597c478bd9Sstevel@tonic-gate  */
19607c478bd9Sstevel@tonic-gate   if(!glh)
19617c478bd9Sstevel@tonic-gate     return 0;
19627c478bd9Sstevel@tonic-gate /*
19637c478bd9Sstevel@tonic-gate  * Search for the line that has the specified ID.
19647c478bd9Sstevel@tonic-gate  */
19657c478bd9Sstevel@tonic-gate   node = _glh_find_id(glh, id);
19667c478bd9Sstevel@tonic-gate /*
19677c478bd9Sstevel@tonic-gate  * Not found?
19687c478bd9Sstevel@tonic-gate  */
19697c478bd9Sstevel@tonic-gate   if(!node)
19707c478bd9Sstevel@tonic-gate     return 0;
19717c478bd9Sstevel@tonic-gate /*
19727c478bd9Sstevel@tonic-gate  * Has the history line been requested?
19737c478bd9Sstevel@tonic-gate  */
19747c478bd9Sstevel@tonic-gate   if(line) {
19757c478bd9Sstevel@tonic-gate /*
19767c478bd9Sstevel@tonic-gate  * If necessary, reallocate the lookup buffer to accomodate the size of
19777c478bd9Sstevel@tonic-gate  * a copy of the located line.
19787c478bd9Sstevel@tonic-gate  */
19797c478bd9Sstevel@tonic-gate     if(node->line->len + 1 > glh->lbuf_dim) {
19807c478bd9Sstevel@tonic-gate       int lbuf_dim = node->line->len + 1;
19817c478bd9Sstevel@tonic-gate       char *lbuf = realloc(glh->lbuf, lbuf_dim);
19827c478bd9Sstevel@tonic-gate       if(!lbuf) {
19837c478bd9Sstevel@tonic-gate 	errno = ENOMEM;
19847c478bd9Sstevel@tonic-gate 	return 0;
19857c478bd9Sstevel@tonic-gate       };
19867c478bd9Sstevel@tonic-gate       glh->lbuf_dim = lbuf_dim;
19877c478bd9Sstevel@tonic-gate       glh->lbuf = lbuf;
19887c478bd9Sstevel@tonic-gate     };
19897c478bd9Sstevel@tonic-gate /*
19907c478bd9Sstevel@tonic-gate  * Copy the history line into the lookup buffer.
19917c478bd9Sstevel@tonic-gate  */
19927c478bd9Sstevel@tonic-gate     _glh_return_line(node->line, glh->lbuf, glh->lbuf_dim);
19937c478bd9Sstevel@tonic-gate /*
19947c478bd9Sstevel@tonic-gate  * Assign the lookup buffer as the returned line pointer.
19957c478bd9Sstevel@tonic-gate  */
19967c478bd9Sstevel@tonic-gate     *line = glh->lbuf;
1997*55fea89dSDan Cross   };
19987c478bd9Sstevel@tonic-gate /*
19997c478bd9Sstevel@tonic-gate  * Does the caller want to know the group of the line?
20007c478bd9Sstevel@tonic-gate  */
20017c478bd9Sstevel@tonic-gate   if(group)
20027c478bd9Sstevel@tonic-gate     *group = node->group;
20037c478bd9Sstevel@tonic-gate /*
20047c478bd9Sstevel@tonic-gate  * Does the caller want to know the timestamp of the line?
20057c478bd9Sstevel@tonic-gate  */
20067c478bd9Sstevel@tonic-gate   if(timestamp)
20077c478bd9Sstevel@tonic-gate     *timestamp = node->timestamp;
20087c478bd9Sstevel@tonic-gate   return 1;
20097c478bd9Sstevel@tonic-gate }
20107c478bd9Sstevel@tonic-gate 
20117c478bd9Sstevel@tonic-gate /*.......................................................................
20127c478bd9Sstevel@tonic-gate  * Lookup a node in the history list by its ID.
20137c478bd9Sstevel@tonic-gate  *
20147c478bd9Sstevel@tonic-gate  * Input:
20157c478bd9Sstevel@tonic-gate  *  glh       GlHistory *  The input-line history maintenance object.
20167c478bd9Sstevel@tonic-gate  *  id        GlhLineID    The ID of the line to be returned.
20177c478bd9Sstevel@tonic-gate  * Output:
20187c478bd9Sstevel@tonic-gate  *  return  GlhLIneNode *  The located node, or NULL if not found.
20197c478bd9Sstevel@tonic-gate  */
_glh_find_id(GlHistory * glh,GlhLineID id)20207c478bd9Sstevel@tonic-gate static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id)
20217c478bd9Sstevel@tonic-gate {
20227c478bd9Sstevel@tonic-gate   GlhLineNode *node;  /* The node being checked */
20237c478bd9Sstevel@tonic-gate /*
20247c478bd9Sstevel@tonic-gate  * Is history enabled?
20257c478bd9Sstevel@tonic-gate  */
20267c478bd9Sstevel@tonic-gate   if(!glh->enable || !glh->list.head)
20277c478bd9Sstevel@tonic-gate     return NULL;
20287c478bd9Sstevel@tonic-gate /*
20297c478bd9Sstevel@tonic-gate  * If possible, start at the end point of the last ID search.
20307c478bd9Sstevel@tonic-gate  * Otherwise start from the head of the list.
20317c478bd9Sstevel@tonic-gate  */
20327c478bd9Sstevel@tonic-gate   node = glh->id_node;
20337c478bd9Sstevel@tonic-gate   if(!node)
20347c478bd9Sstevel@tonic-gate     node = glh->list.head;
20357c478bd9Sstevel@tonic-gate /*
20367c478bd9Sstevel@tonic-gate  * Search forwards from 'node'?
20377c478bd9Sstevel@tonic-gate  */
20387c478bd9Sstevel@tonic-gate   if(node->id < id) {
20397c478bd9Sstevel@tonic-gate     while(node && node->id != id)
20407c478bd9Sstevel@tonic-gate       node = node->next;
20417c478bd9Sstevel@tonic-gate     glh->id_node = node ? node : glh->list.tail;
20427c478bd9Sstevel@tonic-gate /*
20437c478bd9Sstevel@tonic-gate  * Search backwards from 'node'?
20447c478bd9Sstevel@tonic-gate  */
20457c478bd9Sstevel@tonic-gate   } else {
20467c478bd9Sstevel@tonic-gate     while(node && node->id != id)
20477c478bd9Sstevel@tonic-gate       node = node->prev;
20487c478bd9Sstevel@tonic-gate     glh->id_node = node ? node : glh->list.head;
20497c478bd9Sstevel@tonic-gate   };
20507c478bd9Sstevel@tonic-gate /*
20517c478bd9Sstevel@tonic-gate  * Return the located node (this will be NULL if the ID wasn't found).
20527c478bd9Sstevel@tonic-gate  */
20537c478bd9Sstevel@tonic-gate   return node;
20547c478bd9Sstevel@tonic-gate }
20557c478bd9Sstevel@tonic-gate 
20567c478bd9Sstevel@tonic-gate /*.......................................................................
20577c478bd9Sstevel@tonic-gate  * Query the state of the history list. Note that any of the input/output
20587c478bd9Sstevel@tonic-gate  * pointers can be specified as NULL.
20597c478bd9Sstevel@tonic-gate  *
20607c478bd9Sstevel@tonic-gate  * Input:
20617c478bd9Sstevel@tonic-gate  *  glh         GlHistory *  The input-line history maintenance object.
20627c478bd9Sstevel@tonic-gate  * Input/Output:
20637c478bd9Sstevel@tonic-gate  *  enabled           int *  If history is enabled, *enabled will be
20647c478bd9Sstevel@tonic-gate  *                           set to 1. Otherwise it will be assigned 0.
20657c478bd9Sstevel@tonic-gate  *  group        unsigned *  The current history group ID will be assigned
20667c478bd9Sstevel@tonic-gate  *                           to *group.
20677c478bd9Sstevel@tonic-gate  *  max_lines         int *  The currently requested limit on the number
20687c478bd9Sstevel@tonic-gate  *                           of history lines in the list, or -1 if
20697c478bd9Sstevel@tonic-gate  *                           unlimited.
20707c478bd9Sstevel@tonic-gate  */
_glh_state_of_history(GlHistory * glh,int * enabled,unsigned * group,int * max_lines)20717c478bd9Sstevel@tonic-gate void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group,
20727c478bd9Sstevel@tonic-gate 			   int *max_lines)
20737c478bd9Sstevel@tonic-gate {
20747c478bd9Sstevel@tonic-gate   if(glh) {
20757c478bd9Sstevel@tonic-gate     if(enabled)
20767c478bd9Sstevel@tonic-gate      *enabled = glh->enable;
20777c478bd9Sstevel@tonic-gate     if(group)
20787c478bd9Sstevel@tonic-gate      *group = glh->group;
20797c478bd9Sstevel@tonic-gate     if(max_lines)
20807c478bd9Sstevel@tonic-gate      *max_lines = glh->max_lines;
20817c478bd9Sstevel@tonic-gate   };
20827c478bd9Sstevel@tonic-gate }
20837c478bd9Sstevel@tonic-gate 
20847c478bd9Sstevel@tonic-gate /*.......................................................................
20857c478bd9Sstevel@tonic-gate  * Get the range of lines in the history buffer.
20867c478bd9Sstevel@tonic-gate  *
20877c478bd9Sstevel@tonic-gate  * Input:
20887c478bd9Sstevel@tonic-gate  *  glh         GlHistory *  The input-line history maintenance object.
20897c478bd9Sstevel@tonic-gate  * Input/Output:
20907c478bd9Sstevel@tonic-gate  *  oldest  unsigned long *  The sequential entry number of the oldest
20917c478bd9Sstevel@tonic-gate  *                           line in the history list will be assigned
20927c478bd9Sstevel@tonic-gate  *                           to *oldest, unless there are no lines, in
20937c478bd9Sstevel@tonic-gate  *                           which case 0 will be assigned.
20947c478bd9Sstevel@tonic-gate  *  newest  unsigned long *  The sequential entry number of the newest
20957c478bd9Sstevel@tonic-gate  *                           line in the history list will be assigned
20967c478bd9Sstevel@tonic-gate  *                           to *newest, unless there are no lines, in
20977c478bd9Sstevel@tonic-gate  *                           which case 0 will be assigned.
20987c478bd9Sstevel@tonic-gate  *  nlines            int *  The number of lines currently in the history
20997c478bd9Sstevel@tonic-gate  *                           list.
21007c478bd9Sstevel@tonic-gate  */
_glh_range_of_history(GlHistory * glh,unsigned long * oldest,unsigned long * newest,int * nlines)21017c478bd9Sstevel@tonic-gate void _glh_range_of_history(GlHistory *glh, unsigned long *oldest,
21027c478bd9Sstevel@tonic-gate 			   unsigned long *newest, int *nlines)
21037c478bd9Sstevel@tonic-gate {
21047c478bd9Sstevel@tonic-gate   if(glh) {
21057c478bd9Sstevel@tonic-gate     if(oldest)
21067c478bd9Sstevel@tonic-gate       *oldest = glh->list.head ? glh->list.head->id : 0;
21077c478bd9Sstevel@tonic-gate     if(newest)
21087c478bd9Sstevel@tonic-gate       *newest = glh->list.tail ? glh->list.tail->id : 0;
21097c478bd9Sstevel@tonic-gate     if(nlines)
21107c478bd9Sstevel@tonic-gate       *nlines = glh->nline;
21117c478bd9Sstevel@tonic-gate   };
21127c478bd9Sstevel@tonic-gate }
21137c478bd9Sstevel@tonic-gate 
21147c478bd9Sstevel@tonic-gate /*.......................................................................
21157c478bd9Sstevel@tonic-gate  * Return the size of the history buffer and the amount of the
21167c478bd9Sstevel@tonic-gate  * buffer that is currently in use.
21177c478bd9Sstevel@tonic-gate  *
21187c478bd9Sstevel@tonic-gate  * Input:
21197c478bd9Sstevel@tonic-gate  *  glh      GlHistory *  The input-line history maintenance object.
21207c478bd9Sstevel@tonic-gate  * Input/Output:
21217c478bd9Sstevel@tonic-gate  *  buff_size   size_t *  The size of the history buffer (bytes).
21227c478bd9Sstevel@tonic-gate  *  buff_used   size_t *  The amount of the history buffer that
21237c478bd9Sstevel@tonic-gate  *                        is currently occupied (bytes).
21247c478bd9Sstevel@tonic-gate  */
_glh_size_of_history(GlHistory * glh,size_t * buff_size,size_t * buff_used)21257c478bd9Sstevel@tonic-gate void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used)
21267c478bd9Sstevel@tonic-gate {
21277c478bd9Sstevel@tonic-gate   if(glh) {
21287c478bd9Sstevel@tonic-gate     if(buff_size)
21297c478bd9Sstevel@tonic-gate       *buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE;
21307c478bd9Sstevel@tonic-gate /*
21317c478bd9Sstevel@tonic-gate  * Determine the amount of buffer space that is currently occupied.
21327c478bd9Sstevel@tonic-gate  */
21337c478bd9Sstevel@tonic-gate     if(buff_used)
21347c478bd9Sstevel@tonic-gate       *buff_used = glh->nbusy * GLH_SEG_SIZE;
21357c478bd9Sstevel@tonic-gate   };
21367c478bd9Sstevel@tonic-gate }
21377c478bd9Sstevel@tonic-gate 
21387c478bd9Sstevel@tonic-gate /*.......................................................................
21397c478bd9Sstevel@tonic-gate  * Return extra information (ie. in addition to that provided by errno)
21407c478bd9Sstevel@tonic-gate  * about the last error to occur in any of the public functions of this
21417c478bd9Sstevel@tonic-gate  * module.
21427c478bd9Sstevel@tonic-gate  *
21437c478bd9Sstevel@tonic-gate  * Input:
21447c478bd9Sstevel@tonic-gate  *  glh      GlHistory *  The container of the history list.
21457c478bd9Sstevel@tonic-gate  * Output:
21467c478bd9Sstevel@tonic-gate  *  return  const char *  A pointer to the internal buffer in which
21477c478bd9Sstevel@tonic-gate  *                        the error message is temporarily stored.
21487c478bd9Sstevel@tonic-gate  */
_glh_last_error(GlHistory * glh)21497c478bd9Sstevel@tonic-gate const char *_glh_last_error(GlHistory *glh)
21507c478bd9Sstevel@tonic-gate {
21517c478bd9Sstevel@tonic-gate   return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument";
21527c478bd9Sstevel@tonic-gate }
21537c478bd9Sstevel@tonic-gate 
21547c478bd9Sstevel@tonic-gate /*.......................................................................
21557c478bd9Sstevel@tonic-gate  * Unless already stored, store a copy of the line in the history buffer,
21567c478bd9Sstevel@tonic-gate  * then return a reference-counted hash-node pointer to this copy.
21577c478bd9Sstevel@tonic-gate  *
21587c478bd9Sstevel@tonic-gate  * Input:
21597c478bd9Sstevel@tonic-gate  *  glh       GlHistory *   The history maintenance buffer.
21607c478bd9Sstevel@tonic-gate  *  line     const char *   The history line to be recorded.
21617c478bd9Sstevel@tonic-gate  *  n            size_t     The length of the string, excluding any '\0'
21627c478bd9Sstevel@tonic-gate  *                          terminator.
21637c478bd9Sstevel@tonic-gate  * Output:
21647c478bd9Sstevel@tonic-gate  *  return  GlhHashNode *   The hash-node containing the stored line, or
21657c478bd9Sstevel@tonic-gate  *                          NULL on error.
21667c478bd9Sstevel@tonic-gate  */
_glh_acquire_copy(GlHistory * glh,const char * line,size_t n)21677c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line,
21687c478bd9Sstevel@tonic-gate 				      size_t n)
21697c478bd9Sstevel@tonic-gate {
21707c478bd9Sstevel@tonic-gate   GlhHashBucket *bucket;   /* The hash-table bucket of the line */
21717c478bd9Sstevel@tonic-gate   GlhHashNode *hnode;      /* The hash-table node of the line */
21727c478bd9Sstevel@tonic-gate   int i;
21737c478bd9Sstevel@tonic-gate /*
21747c478bd9Sstevel@tonic-gate  * In which bucket should the line be recorded?
21757c478bd9Sstevel@tonic-gate  */
21767c478bd9Sstevel@tonic-gate   bucket = glh_find_bucket(glh, line, n);
21777c478bd9Sstevel@tonic-gate /*
21787c478bd9Sstevel@tonic-gate  * Is the line already recorded there?
21797c478bd9Sstevel@tonic-gate  */
21807c478bd9Sstevel@tonic-gate   hnode = glh_find_hash_node(bucket, line, n);
21817c478bd9Sstevel@tonic-gate /*
21827c478bd9Sstevel@tonic-gate  * If the line isn't recorded in the buffer yet, make room for it.
21837c478bd9Sstevel@tonic-gate  */
21847c478bd9Sstevel@tonic-gate   if(!hnode) {
21857c478bd9Sstevel@tonic-gate     GlhLineSeg *seg;   /* A line segment */
21867c478bd9Sstevel@tonic-gate     int offset;        /* An offset into line[] */
21877c478bd9Sstevel@tonic-gate /*
21887c478bd9Sstevel@tonic-gate  * How many string segments will be needed to record the new line,
21897c478bd9Sstevel@tonic-gate  * including space for a '\0' terminator?
21907c478bd9Sstevel@tonic-gate  */
21917c478bd9Sstevel@tonic-gate     int nseg = ((n+1) + GLH_SEG_SIZE-1) /  GLH_SEG_SIZE;
21927c478bd9Sstevel@tonic-gate /*
21937c478bd9Sstevel@tonic-gate  * Discard the oldest history lines in the buffer until at least
21947c478bd9Sstevel@tonic-gate  * 'nseg' segments have been freed up, or until we run out of buffer
21957c478bd9Sstevel@tonic-gate  * space.
21967c478bd9Sstevel@tonic-gate  */
21977c478bd9Sstevel@tonic-gate     while(glh->nfree < nseg && glh->nbusy > 0)
21987c478bd9Sstevel@tonic-gate       _glh_discard_line(glh, glh->list.head);
21997c478bd9Sstevel@tonic-gate /*
22007c478bd9Sstevel@tonic-gate  * If the buffer is smaller than the new line, don't attempt to truncate
22017c478bd9Sstevel@tonic-gate  * it to fit. Simply don't archive it.
22027c478bd9Sstevel@tonic-gate  */
22037c478bd9Sstevel@tonic-gate     if(glh->nfree < nseg)
22047c478bd9Sstevel@tonic-gate       return NULL;
22057c478bd9Sstevel@tonic-gate /*
22067c478bd9Sstevel@tonic-gate  * Record the line in the first 'nseg' segments of the list of unused segments.
22077c478bd9Sstevel@tonic-gate  */
22087c478bd9Sstevel@tonic-gate     offset = 0;
22097c478bd9Sstevel@tonic-gate     for(i=0,seg=glh->unused; i<nseg-1; i++,seg=seg->next, offset+=GLH_SEG_SIZE)
22107c478bd9Sstevel@tonic-gate       memcpy(seg->s, line + offset, GLH_SEG_SIZE);
22117c478bd9Sstevel@tonic-gate     memcpy(seg->s, line + offset, n-offset);
22127c478bd9Sstevel@tonic-gate     seg->s[n-offset] = '\0';
22137c478bd9Sstevel@tonic-gate /*
22147c478bd9Sstevel@tonic-gate  * Create a new hash-node for the line.
22157c478bd9Sstevel@tonic-gate  */
22167c478bd9Sstevel@tonic-gate     hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem);
22177c478bd9Sstevel@tonic-gate     if(!hnode)
22187c478bd9Sstevel@tonic-gate       return NULL;
22197c478bd9Sstevel@tonic-gate /*
22207c478bd9Sstevel@tonic-gate  * Move the copy of the line from the list of unused segments to
22217c478bd9Sstevel@tonic-gate  * the hash node.
22227c478bd9Sstevel@tonic-gate  */
22237c478bd9Sstevel@tonic-gate     hnode->head = glh->unused;
22247c478bd9Sstevel@tonic-gate     glh->unused = seg->next;
22257c478bd9Sstevel@tonic-gate     seg->next = NULL;
22267c478bd9Sstevel@tonic-gate     glh->nbusy += nseg;
22277c478bd9Sstevel@tonic-gate     glh->nfree -= nseg;
22287c478bd9Sstevel@tonic-gate /*
22297c478bd9Sstevel@tonic-gate  * Prepend the new hash node to the list within the associated bucket.
22307c478bd9Sstevel@tonic-gate  */
22317c478bd9Sstevel@tonic-gate     hnode->next = bucket->lines;
22327c478bd9Sstevel@tonic-gate     bucket->lines = hnode;
22337c478bd9Sstevel@tonic-gate /*
22347c478bd9Sstevel@tonic-gate  * Initialize the rest of the members of the hash node.
22357c478bd9Sstevel@tonic-gate  */
22367c478bd9Sstevel@tonic-gate     hnode->len = n;
22377c478bd9Sstevel@tonic-gate     hnode->reported = 0;
22387c478bd9Sstevel@tonic-gate     hnode->used = 0;
22397c478bd9Sstevel@tonic-gate     hnode->bucket = bucket;
22407c478bd9Sstevel@tonic-gate   };
22417c478bd9Sstevel@tonic-gate /*
22427c478bd9Sstevel@tonic-gate  * Increment the reference count of the line.
22437c478bd9Sstevel@tonic-gate  */
22447c478bd9Sstevel@tonic-gate   hnode->used++;
22457c478bd9Sstevel@tonic-gate   return hnode;
22467c478bd9Sstevel@tonic-gate }
22477c478bd9Sstevel@tonic-gate 
22487c478bd9Sstevel@tonic-gate /*.......................................................................
22497c478bd9Sstevel@tonic-gate  * Decrement the reference count of the history line of a given hash-node,
22507c478bd9Sstevel@tonic-gate  * and if the count reaches zero, delete both the hash-node and the
22517c478bd9Sstevel@tonic-gate  * buffered copy of the line.
22527c478bd9Sstevel@tonic-gate  *
22537c478bd9Sstevel@tonic-gate  * Input:
22547c478bd9Sstevel@tonic-gate  *  glh      GlHistory *  The history container object.
22557c478bd9Sstevel@tonic-gate  *  hnode  GlhHashNode *  The node to be removed.
22567c478bd9Sstevel@tonic-gate  * Output:
22577c478bd9Sstevel@tonic-gate  *  return GlhHashNode *  The deleted hash-node (ie. NULL).
22587c478bd9Sstevel@tonic-gate  */
_glh_discard_copy(GlHistory * glh,GlhHashNode * hnode)22597c478bd9Sstevel@tonic-gate static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode)
22607c478bd9Sstevel@tonic-gate {
22617c478bd9Sstevel@tonic-gate   if(hnode) {
22627c478bd9Sstevel@tonic-gate     GlhHashBucket *bucket = hnode->bucket;
22637c478bd9Sstevel@tonic-gate /*
22647c478bd9Sstevel@tonic-gate  * If decrementing the reference count of the hash-node doesn't reduce
22657c478bd9Sstevel@tonic-gate  * the reference count to zero, then the line is still in use in another
22667c478bd9Sstevel@tonic-gate  * object, so don't delete it yet. Return NULL to indicate that the caller's
22677c478bd9Sstevel@tonic-gate  * access to the hash-node copy has been deleted.
22687c478bd9Sstevel@tonic-gate  */
22697c478bd9Sstevel@tonic-gate     if(--hnode->used >= 1)
22707c478bd9Sstevel@tonic-gate       return NULL;
22717c478bd9Sstevel@tonic-gate /*
22727c478bd9Sstevel@tonic-gate  * Remove the hash-node from the list in its parent bucket.
22737c478bd9Sstevel@tonic-gate  */
22747c478bd9Sstevel@tonic-gate     if(bucket->lines == hnode) {
22757c478bd9Sstevel@tonic-gate       bucket->lines = hnode->next;
22767c478bd9Sstevel@tonic-gate     } else {
22777c478bd9Sstevel@tonic-gate       GlhHashNode *prev;    /* The node which precedes hnode in the bucket */
22787c478bd9Sstevel@tonic-gate       for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next)
22797c478bd9Sstevel@tonic-gate 	;
22807c478bd9Sstevel@tonic-gate       if(prev)
22817c478bd9Sstevel@tonic-gate 	prev->next = hnode->next;
22827c478bd9Sstevel@tonic-gate     };
22837c478bd9Sstevel@tonic-gate     hnode->next = NULL;
22847c478bd9Sstevel@tonic-gate /*
22857c478bd9Sstevel@tonic-gate  * Return the line segments of the hash-node to the list of unused segments.
22867c478bd9Sstevel@tonic-gate  */
22877c478bd9Sstevel@tonic-gate     if(hnode->head) {
22887c478bd9Sstevel@tonic-gate       GlhLineSeg *tail; /* The last node in the list of line segments */
22897c478bd9Sstevel@tonic-gate       int nseg;         /* The number of segments being discarded */
22907c478bd9Sstevel@tonic-gate /*
22917c478bd9Sstevel@tonic-gate  * Get the last node of the list of line segments referenced in the hash-node,
22927c478bd9Sstevel@tonic-gate  * while counting the number of line segments used.
22937c478bd9Sstevel@tonic-gate  */
22947c478bd9Sstevel@tonic-gate       for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next)
22957c478bd9Sstevel@tonic-gate 	;
22967c478bd9Sstevel@tonic-gate /*
22977c478bd9Sstevel@tonic-gate  * Prepend the list of line segments used by the hash node to the
22987c478bd9Sstevel@tonic-gate  * list of unused line segments.
22997c478bd9Sstevel@tonic-gate  */
23007c478bd9Sstevel@tonic-gate       tail->next = glh->unused;
23017c478bd9Sstevel@tonic-gate       glh->unused = hnode->head;
23027c478bd9Sstevel@tonic-gate       glh->nbusy -= nseg;
23037c478bd9Sstevel@tonic-gate       glh->nfree += nseg;
23047c478bd9Sstevel@tonic-gate     };
23057c478bd9Sstevel@tonic-gate /*
23067c478bd9Sstevel@tonic-gate  * Return the container of the hash-node to the freelist.
23077c478bd9Sstevel@tonic-gate  */
23087c478bd9Sstevel@tonic-gate     hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode);
23097c478bd9Sstevel@tonic-gate   };
23107c478bd9Sstevel@tonic-gate   return NULL;
23117c478bd9Sstevel@tonic-gate }
23127c478bd9Sstevel@tonic-gate 
23137c478bd9Sstevel@tonic-gate /*.......................................................................
23147c478bd9Sstevel@tonic-gate  * Private function to locate the hash bucket associated with a given
23157c478bd9Sstevel@tonic-gate  * history line.
23167c478bd9Sstevel@tonic-gate  *
23177c478bd9Sstevel@tonic-gate  * This uses a hash-function described in the dragon-book
23187c478bd9Sstevel@tonic-gate  * ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and
23197c478bd9Sstevel@tonic-gate  *  Ullman; pub. Adison Wesley) page 435.
23207c478bd9Sstevel@tonic-gate  *
23217c478bd9Sstevel@tonic-gate  * Input:
23227c478bd9Sstevel@tonic-gate  *  glh        GlHistory *   The history container object.
23237c478bd9Sstevel@tonic-gate  *  line      const char *   The historical line to look up.
23247c478bd9Sstevel@tonic-gate  *  n             size_t     The length of the line in line[], excluding
23257c478bd9Sstevel@tonic-gate  *                           any '\0' terminator.
23267c478bd9Sstevel@tonic-gate  * Output:
23277c478bd9Sstevel@tonic-gate  *  return GlhHashBucket *   The located hash-bucket.
23287c478bd9Sstevel@tonic-gate  */
glh_find_bucket(GlHistory * glh,const char * line,size_t n)23297c478bd9Sstevel@tonic-gate static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line,
23307c478bd9Sstevel@tonic-gate 				      size_t n)
23317c478bd9Sstevel@tonic-gate {
23327c478bd9Sstevel@tonic-gate   unsigned long h = 0L;
23337c478bd9Sstevel@tonic-gate   int i;
23347c478bd9Sstevel@tonic-gate   for(i=0; i<n; i++) {
23357c478bd9Sstevel@tonic-gate     unsigned char c = line[i];
23367c478bd9Sstevel@tonic-gate     h = 65599UL * h + c;  /* 65599 is a prime close to 2^16 */
23377c478bd9Sstevel@tonic-gate   };
23387c478bd9Sstevel@tonic-gate   return glh->hash.bucket + (h % GLH_HASH_SIZE);
23397c478bd9Sstevel@tonic-gate }
23407c478bd9Sstevel@tonic-gate 
23417c478bd9Sstevel@tonic-gate /*.......................................................................
23427c478bd9Sstevel@tonic-gate  * Find a given history line within a given hash-table bucket.
23437c478bd9Sstevel@tonic-gate  *
23447c478bd9Sstevel@tonic-gate  * Input:
23457c478bd9Sstevel@tonic-gate  *  bucket  GlhHashBucket *  The hash-table bucket in which to search.
23467c478bd9Sstevel@tonic-gate  *  line       const char *  The historical line to lookup.
23477c478bd9Sstevel@tonic-gate  *  n             size_t     The length of the line in line[], excluding
23487c478bd9Sstevel@tonic-gate  *                           any '\0' terminator.
23497c478bd9Sstevel@tonic-gate  * Output:
23507c478bd9Sstevel@tonic-gate  *  return    GlhHashNode *  The hash-table entry of the line, or NULL
23517c478bd9Sstevel@tonic-gate  *                           if not found.
23527c478bd9Sstevel@tonic-gate  */
glh_find_hash_node(GlhHashBucket * bucket,const char * line,size_t n)23537c478bd9Sstevel@tonic-gate static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line,
23547c478bd9Sstevel@tonic-gate 				       size_t n)
23557c478bd9Sstevel@tonic-gate {
23567c478bd9Sstevel@tonic-gate   GlhHashNode *node;  /* A node in the list of lines in the bucket */
23577c478bd9Sstevel@tonic-gate /*
23587c478bd9Sstevel@tonic-gate  * Compare each of the lines in the list of lines, against 'line'.
23597c478bd9Sstevel@tonic-gate  */
23607c478bd9Sstevel@tonic-gate   for(node=bucket->lines; node; node=node->next) {
23617c478bd9Sstevel@tonic-gate     if(_glh_is_line(node, line, n))
23627c478bd9Sstevel@tonic-gate       return node;
23637c478bd9Sstevel@tonic-gate   };
23647c478bd9Sstevel@tonic-gate   return NULL;
23657c478bd9Sstevel@tonic-gate }
23667c478bd9Sstevel@tonic-gate 
23677c478bd9Sstevel@tonic-gate /*.......................................................................
23687c478bd9Sstevel@tonic-gate  * Return non-zero if a given string is equal to a given segmented line
23697c478bd9Sstevel@tonic-gate  * node.
23707c478bd9Sstevel@tonic-gate  *
23717c478bd9Sstevel@tonic-gate  * Input:
23727c478bd9Sstevel@tonic-gate  *  hash   GlhHashNode *   The hash-table entry of the line.
23737c478bd9Sstevel@tonic-gate  *  line    const char *   The string to be compared to the segmented
23747c478bd9Sstevel@tonic-gate  *                         line.
23757c478bd9Sstevel@tonic-gate  *  n           size_t     The length of the line in line[], excluding
23767c478bd9Sstevel@tonic-gate  *                         any '\0' terminator.
23777c478bd9Sstevel@tonic-gate  * Output:
23787c478bd9Sstevel@tonic-gate  *  return         int     0 - The lines differ.
23797c478bd9Sstevel@tonic-gate  *                         1 - The lines are the same.
23807c478bd9Sstevel@tonic-gate  */
_glh_is_line(GlhHashNode * hash,const char * line,size_t n)23817c478bd9Sstevel@tonic-gate static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n)
23827c478bd9Sstevel@tonic-gate {
23837c478bd9Sstevel@tonic-gate   GlhLineSeg *seg;   /* A node in the list of line segments */
23847c478bd9Sstevel@tonic-gate   int i;
23857c478bd9Sstevel@tonic-gate /*
23867c478bd9Sstevel@tonic-gate  * Do the two lines have the same length?
23877c478bd9Sstevel@tonic-gate  */
23887c478bd9Sstevel@tonic-gate   if(n != hash->len)
23897c478bd9Sstevel@tonic-gate     return 0;
23907c478bd9Sstevel@tonic-gate /*
23917c478bd9Sstevel@tonic-gate  * Compare the characters of the segmented and unsegmented versions
23927c478bd9Sstevel@tonic-gate  * of the line.
23937c478bd9Sstevel@tonic-gate  */
23947c478bd9Sstevel@tonic-gate   for(seg=hash->head; n>0 && seg; seg=seg->next) {
23957c478bd9Sstevel@tonic-gate     const char *s = seg->s;
23967c478bd9Sstevel@tonic-gate     for(i=0; n>0 && i<GLH_SEG_SIZE; i++,n--) {
23977c478bd9Sstevel@tonic-gate       if(*line++ != *s++)
23987c478bd9Sstevel@tonic-gate 	return 0;
23997c478bd9Sstevel@tonic-gate     };
24007c478bd9Sstevel@tonic-gate   };
24017c478bd9Sstevel@tonic-gate   return 1;
24027c478bd9Sstevel@tonic-gate }
24037c478bd9Sstevel@tonic-gate 
24047c478bd9Sstevel@tonic-gate /*.......................................................................
24057c478bd9Sstevel@tonic-gate  * Return non-zero if a given line has the specified segmented search
24067c478bd9Sstevel@tonic-gate  * prefix.
24077c478bd9Sstevel@tonic-gate  *
24087c478bd9Sstevel@tonic-gate  * Input:
24097c478bd9Sstevel@tonic-gate  *  line   GlhHashNode *   The line to be compared against the prefix.
24107c478bd9Sstevel@tonic-gate  *  prefix GlhHashNode *   The search prefix, or NULL to match any string.
24117c478bd9Sstevel@tonic-gate  * Output:
24127c478bd9Sstevel@tonic-gate  *  return         int     0 - The line doesn't have the specified prefix.
24137c478bd9Sstevel@tonic-gate  *                         1 - The line has the specified prefix.
24147c478bd9Sstevel@tonic-gate  */
_glh_line_matches_prefix(GlhHashNode * line,GlhHashNode * prefix)24157c478bd9Sstevel@tonic-gate static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix)
24167c478bd9Sstevel@tonic-gate {
24177c478bd9Sstevel@tonic-gate   GlhLineStream lstr; /* The stream that is used to traverse 'line' */
24187c478bd9Sstevel@tonic-gate   GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */
24197c478bd9Sstevel@tonic-gate /*
24207c478bd9Sstevel@tonic-gate  * When prefix==NULL, this means that the nul string
24217c478bd9Sstevel@tonic-gate  * is to be matched, and this matches all lines.
24227c478bd9Sstevel@tonic-gate  */
24237c478bd9Sstevel@tonic-gate   if(!prefix)
24247c478bd9Sstevel@tonic-gate     return 1;
24257c478bd9Sstevel@tonic-gate /*
24267c478bd9Sstevel@tonic-gate  * Wrap the two history lines that are to be compared in iterator
24277c478bd9Sstevel@tonic-gate  * stream objects.
24287c478bd9Sstevel@tonic-gate  */
24297c478bd9Sstevel@tonic-gate   glh_init_stream(&lstr, line);
24307c478bd9Sstevel@tonic-gate   glh_init_stream(&pstr, prefix);
24317c478bd9Sstevel@tonic-gate /*
24327c478bd9Sstevel@tonic-gate  * If the prefix contains a glob pattern, match the prefix as a glob
24337c478bd9Sstevel@tonic-gate  * pattern.
24347c478bd9Sstevel@tonic-gate  */
24357c478bd9Sstevel@tonic-gate   if(glh_contains_glob(prefix))
24367c478bd9Sstevel@tonic-gate     return glh_line_matches_glob(&lstr, &pstr);
24377c478bd9Sstevel@tonic-gate /*
24387c478bd9Sstevel@tonic-gate  * Is the prefix longer than the line being compared against it?
24397c478bd9Sstevel@tonic-gate  */
24407c478bd9Sstevel@tonic-gate   if(prefix->len > line->len)
24417c478bd9Sstevel@tonic-gate     return 0;
24427c478bd9Sstevel@tonic-gate /*
24437c478bd9Sstevel@tonic-gate  * Compare the line to the prefix.
24447c478bd9Sstevel@tonic-gate  */
24457c478bd9Sstevel@tonic-gate   while(pstr.c != '\0' && pstr.c == lstr.c) {
24467c478bd9Sstevel@tonic-gate     glh_step_stream(&lstr);
24477c478bd9Sstevel@tonic-gate     glh_step_stream(&pstr);
24487c478bd9Sstevel@tonic-gate   };
24497c478bd9Sstevel@tonic-gate /*
24507c478bd9Sstevel@tonic-gate  * Did we reach the end of the prefix string before finding
24517c478bd9Sstevel@tonic-gate  * any differences?
24527c478bd9Sstevel@tonic-gate  */
24537c478bd9Sstevel@tonic-gate   return pstr.c == '\0';
24547c478bd9Sstevel@tonic-gate }
24557c478bd9Sstevel@tonic-gate 
24567c478bd9Sstevel@tonic-gate /*.......................................................................
24577c478bd9Sstevel@tonic-gate  * Copy a given history line into a specified output string.
24587c478bd9Sstevel@tonic-gate  *
24597c478bd9Sstevel@tonic-gate  * Input:
24607c478bd9Sstevel@tonic-gate  *  hash  GlhHashNode    The hash-table entry of the history line to
24617c478bd9Sstevel@tonic-gate  *                       be copied.
24627c478bd9Sstevel@tonic-gate  *  line         char *  A copy of the history line.
24637c478bd9Sstevel@tonic-gate  *  dim        size_t    The allocated dimension of the line buffer.
24647c478bd9Sstevel@tonic-gate  */
_glh_return_line(GlhHashNode * hash,char * line,size_t dim)24657c478bd9Sstevel@tonic-gate static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim)
24667c478bd9Sstevel@tonic-gate {
24677c478bd9Sstevel@tonic-gate   GlhLineSeg *seg;   /* A node in the list of line segments */
24687c478bd9Sstevel@tonic-gate   int i;
24697c478bd9Sstevel@tonic-gate   for(seg=hash->head; dim>0 && seg; seg=seg->next) {
24707c478bd9Sstevel@tonic-gate     const char *s = seg->s;
24717c478bd9Sstevel@tonic-gate     for(i=0; dim>0 && i<GLH_SEG_SIZE; i++,dim--)
24727c478bd9Sstevel@tonic-gate       *line++ = *s++;
24737c478bd9Sstevel@tonic-gate   };
24747c478bd9Sstevel@tonic-gate /*
24757c478bd9Sstevel@tonic-gate  * If the line wouldn't fit in the output buffer, replace the last character
24767c478bd9Sstevel@tonic-gate  * with a '\0' terminator.
24777c478bd9Sstevel@tonic-gate  */
24787c478bd9Sstevel@tonic-gate   if(dim==0)
24797c478bd9Sstevel@tonic-gate     line[-1] = '\0';
24807c478bd9Sstevel@tonic-gate }
24817c478bd9Sstevel@tonic-gate 
24827c478bd9Sstevel@tonic-gate /*.......................................................................
24837c478bd9Sstevel@tonic-gate  * This function should be called whenever a new line recall is
24847c478bd9Sstevel@tonic-gate  * attempted.  It preserves a copy of the current input line in the
24857c478bd9Sstevel@tonic-gate  * history list while other lines in the history list are being
24867c478bd9Sstevel@tonic-gate  * returned.
24877c478bd9Sstevel@tonic-gate  *
24887c478bd9Sstevel@tonic-gate  * Input:
24897c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
24907c478bd9Sstevel@tonic-gate  *  line      char *  The current contents of the input line buffer.
24917c478bd9Sstevel@tonic-gate  * Output:
24927c478bd9Sstevel@tonic-gate  *  return     int    0 - OK.
24937c478bd9Sstevel@tonic-gate  *                    1 - Error.
24947c478bd9Sstevel@tonic-gate  */
_glh_prepare_for_recall(GlHistory * glh,char * line)24957c478bd9Sstevel@tonic-gate static int _glh_prepare_for_recall(GlHistory *glh, char *line)
24967c478bd9Sstevel@tonic-gate {
24977c478bd9Sstevel@tonic-gate /*
24987c478bd9Sstevel@tonic-gate  * If a recall session has already been started, but we have returned
24997c478bd9Sstevel@tonic-gate  * to the preserved copy of the input line, if the user has changed
25007c478bd9Sstevel@tonic-gate  * this line, we should replace the preserved copy of the original
25017c478bd9Sstevel@tonic-gate  * input line with the new one. To do this simply cancel the session,
25027c478bd9Sstevel@tonic-gate  * so that a new session is started below.
25037c478bd9Sstevel@tonic-gate  */
25047c478bd9Sstevel@tonic-gate   if(glh->recall && glh->recall == glh->list.tail &&
25057c478bd9Sstevel@tonic-gate      !_glh_is_line(glh->recall->line, line, strlen(line))) {
25067c478bd9Sstevel@tonic-gate     _glh_cancel_search(glh);
25077c478bd9Sstevel@tonic-gate   };
25087c478bd9Sstevel@tonic-gate /*
25097c478bd9Sstevel@tonic-gate  * If this is the first line recall of a new recall session, save the
25107c478bd9Sstevel@tonic-gate  * current line for potential recall later, and mark it as the last
25117c478bd9Sstevel@tonic-gate  * line recalled.
25127c478bd9Sstevel@tonic-gate  */
25137c478bd9Sstevel@tonic-gate   if(!glh->recall) {
25147c478bd9Sstevel@tonic-gate     if(_glh_add_history(glh, line, 1))
25157c478bd9Sstevel@tonic-gate       return 1;
25167c478bd9Sstevel@tonic-gate     glh->recall = glh->list.tail;
25177c478bd9Sstevel@tonic-gate /*
25187c478bd9Sstevel@tonic-gate  * The above call to _glh_add_history() will have incremented the line
25197c478bd9Sstevel@tonic-gate  * sequence number, after adding the line. Since we only want this to
25207c478bd9Sstevel@tonic-gate  * to be incremented for permanently entered lines, decrement it again.
25217c478bd9Sstevel@tonic-gate  */
25227c478bd9Sstevel@tonic-gate     glh->seq--;
25237c478bd9Sstevel@tonic-gate   };
25247c478bd9Sstevel@tonic-gate   return 0;
25257c478bd9Sstevel@tonic-gate }
25267c478bd9Sstevel@tonic-gate 
25277c478bd9Sstevel@tonic-gate /*.......................................................................
25287c478bd9Sstevel@tonic-gate  * Return non-zero if a history search session is currently in progress.
25297c478bd9Sstevel@tonic-gate  *
25307c478bd9Sstevel@tonic-gate  * Input:
25317c478bd9Sstevel@tonic-gate  *  glh  GlHistory *  The input-line history maintenance object.
25327c478bd9Sstevel@tonic-gate  * Output:
25337c478bd9Sstevel@tonic-gate  *  return     int    0 - No search is currently in progress.
25347c478bd9Sstevel@tonic-gate  *                    1 - A search is in progress.
25357c478bd9Sstevel@tonic-gate  */
_glh_search_active(GlHistory * glh)25367c478bd9Sstevel@tonic-gate int _glh_search_active(GlHistory *glh)
25377c478bd9Sstevel@tonic-gate {
25387c478bd9Sstevel@tonic-gate   return glh && glh->recall;
25397c478bd9Sstevel@tonic-gate }
25407c478bd9Sstevel@tonic-gate 
25417c478bd9Sstevel@tonic-gate /*.......................................................................
25427c478bd9Sstevel@tonic-gate  * Initialize a character iterator object to point to the start of a
25437c478bd9Sstevel@tonic-gate  * given history line. The first character of the line will be placed
25447c478bd9Sstevel@tonic-gate  * in str->c, and subsequent characters can be placed there by calling
25457c478bd9Sstevel@tonic-gate  * glh_strep_stream().
25467c478bd9Sstevel@tonic-gate  *
25477c478bd9Sstevel@tonic-gate  * Input:
25487c478bd9Sstevel@tonic-gate  *  str  GlhLineStream *  The iterator object to be initialized.
25497c478bd9Sstevel@tonic-gate  *  line   GlhHashNode *  The history line to be iterated over (a
25507c478bd9Sstevel@tonic-gate  *                        NULL value here, is interpretted as an
25517c478bd9Sstevel@tonic-gate  *                        empty string by glh_step_stream()).
25527c478bd9Sstevel@tonic-gate  */
glh_init_stream(GlhLineStream * str,GlhHashNode * line)25537c478bd9Sstevel@tonic-gate static void glh_init_stream(GlhLineStream *str, GlhHashNode *line)
25547c478bd9Sstevel@tonic-gate {
25557c478bd9Sstevel@tonic-gate   str->seg = line ? line->head : NULL;
25567c478bd9Sstevel@tonic-gate   str->posn = 0;
25577c478bd9Sstevel@tonic-gate   str->c = str->seg ? str->seg->s[0] : '\0';
25587c478bd9Sstevel@tonic-gate }
25597c478bd9Sstevel@tonic-gate 
25607c478bd9Sstevel@tonic-gate /*.......................................................................
25617c478bd9Sstevel@tonic-gate  * Copy the next unread character in the line being iterated, in str->c.
25627c478bd9Sstevel@tonic-gate  * Once the end of the history line has been reached, all futher calls
25637c478bd9Sstevel@tonic-gate  * set str->c to '\0'.
25647c478bd9Sstevel@tonic-gate  *
25657c478bd9Sstevel@tonic-gate  * Input:
25667c478bd9Sstevel@tonic-gate  *  str   GlhLineStream *  The history-line iterator to read from.
25677c478bd9Sstevel@tonic-gate  */
glh_step_stream(GlhLineStream * str)25687c478bd9Sstevel@tonic-gate static void glh_step_stream(GlhLineStream *str)
25697c478bd9Sstevel@tonic-gate {
25707c478bd9Sstevel@tonic-gate /*
25717c478bd9Sstevel@tonic-gate  * Get the character from the current iterator position within the line.
25727c478bd9Sstevel@tonic-gate  */
25737c478bd9Sstevel@tonic-gate   str->c = str->seg ? str->seg->s[str->posn] : '\0';
25747c478bd9Sstevel@tonic-gate /*
25757c478bd9Sstevel@tonic-gate  * Unless we have reached the end of the string, move the iterator
25767c478bd9Sstevel@tonic-gate  * to the position of the next character in the line.
25777c478bd9Sstevel@tonic-gate  */
25787c478bd9Sstevel@tonic-gate   if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) {
25797c478bd9Sstevel@tonic-gate     str->posn = 0;
25807c478bd9Sstevel@tonic-gate     str->seg = str->seg->next;
25817c478bd9Sstevel@tonic-gate   };
25827c478bd9Sstevel@tonic-gate }
25837c478bd9Sstevel@tonic-gate 
25847c478bd9Sstevel@tonic-gate /*.......................................................................
25857c478bd9Sstevel@tonic-gate  * Return non-zero if the specified search prefix contains any glob
25867c478bd9Sstevel@tonic-gate  * wildcard characters.
25877c478bd9Sstevel@tonic-gate  *
25887c478bd9Sstevel@tonic-gate  * Input:
25897c478bd9Sstevel@tonic-gate  *  prefix   GlhHashNode *  The search prefix.
25907c478bd9Sstevel@tonic-gate  * Output:
25917c478bd9Sstevel@tonic-gate  *  return           int    0 - The prefix doesn't contain any globbing
25927c478bd9Sstevel@tonic-gate  *                              characters.
25937c478bd9Sstevel@tonic-gate  *                          1 - The prefix contains at least one
25947c478bd9Sstevel@tonic-gate  *                              globbing character.
25957c478bd9Sstevel@tonic-gate  */
glh_contains_glob(GlhHashNode * prefix)25967c478bd9Sstevel@tonic-gate static int glh_contains_glob(GlhHashNode *prefix)
25977c478bd9Sstevel@tonic-gate {
25987c478bd9Sstevel@tonic-gate   GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */
25997c478bd9Sstevel@tonic-gate /*
26007c478bd9Sstevel@tonic-gate  * Wrap a stream iterator around the prefix, so that we can traverse it
26017c478bd9Sstevel@tonic-gate  * without worrying about line-segmentation.
26027c478bd9Sstevel@tonic-gate  */
26037c478bd9Sstevel@tonic-gate   glh_init_stream(&pstr, prefix);
26047c478bd9Sstevel@tonic-gate /*
26057c478bd9Sstevel@tonic-gate  * Search for unescaped wildcard characters.
26067c478bd9Sstevel@tonic-gate  */
26077c478bd9Sstevel@tonic-gate   while(pstr.c != '\0') {
26087c478bd9Sstevel@tonic-gate     switch(pstr.c) {
26097c478bd9Sstevel@tonic-gate     case '\\':                      /* Skip escaped characters */
26107c478bd9Sstevel@tonic-gate       glh_step_stream(&pstr);
2611*55fea89dSDan Cross       break;
26127c478bd9Sstevel@tonic-gate     case '*': case '?': case '[':   /* A wildcard character? */
26137c478bd9Sstevel@tonic-gate       return 1;
26147c478bd9Sstevel@tonic-gate       break;
26157c478bd9Sstevel@tonic-gate     };
26167c478bd9Sstevel@tonic-gate     glh_step_stream(&pstr);
26177c478bd9Sstevel@tonic-gate   };
26187c478bd9Sstevel@tonic-gate /*
26197c478bd9Sstevel@tonic-gate  * No wildcard characters were found.
26207c478bd9Sstevel@tonic-gate  */
26217c478bd9Sstevel@tonic-gate   return 0;
26227c478bd9Sstevel@tonic-gate }
26237c478bd9Sstevel@tonic-gate 
26247c478bd9Sstevel@tonic-gate /*.......................................................................
26257c478bd9Sstevel@tonic-gate  * Return non-zero if the history line matches a search prefix containing
26267c478bd9Sstevel@tonic-gate  * a glob pattern.
26277c478bd9Sstevel@tonic-gate  *
26287c478bd9Sstevel@tonic-gate  * Input:
26297c478bd9Sstevel@tonic-gate  *  lstr  GlhLineStream *  The iterator stream being used to traverse
26307c478bd9Sstevel@tonic-gate  *                         the history line that is being matched.
26317c478bd9Sstevel@tonic-gate  *  pstr  GlhLineStream *  The iterator stream being used to traverse
26327c478bd9Sstevel@tonic-gate  *                         the pattern.
26337c478bd9Sstevel@tonic-gate  * Output:
26347c478bd9Sstevel@tonic-gate  *  return    int          0 - Doesn't match.
26357c478bd9Sstevel@tonic-gate  *                         1 - The line matches the pattern.
26367c478bd9Sstevel@tonic-gate  */
glh_line_matches_glob(GlhLineStream * lstr,GlhLineStream * pstr)26377c478bd9Sstevel@tonic-gate static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr)
26387c478bd9Sstevel@tonic-gate {
26397c478bd9Sstevel@tonic-gate /*
26407c478bd9Sstevel@tonic-gate  * Match each character of the pattern until we reach the end of the
26417c478bd9Sstevel@tonic-gate  * pattern.
26427c478bd9Sstevel@tonic-gate  */
26437c478bd9Sstevel@tonic-gate   while(pstr->c != '\0') {
26447c478bd9Sstevel@tonic-gate /*
26457c478bd9Sstevel@tonic-gate  * Handle the next character of the pattern.
26467c478bd9Sstevel@tonic-gate  */
26477c478bd9Sstevel@tonic-gate     switch(pstr->c) {
26487c478bd9Sstevel@tonic-gate /*
26497c478bd9Sstevel@tonic-gate  * A match zero-or-more characters wildcard operator.
26507c478bd9Sstevel@tonic-gate  */
26517c478bd9Sstevel@tonic-gate     case '*':
26527c478bd9Sstevel@tonic-gate /*
26537c478bd9Sstevel@tonic-gate  * Skip the '*' character in the pattern.
26547c478bd9Sstevel@tonic-gate  */
26557c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);
26567c478bd9Sstevel@tonic-gate /*
26577c478bd9Sstevel@tonic-gate  * If the pattern ends with the '*' wildcard, then the
26587c478bd9Sstevel@tonic-gate  * rest of the line matches this.
26597c478bd9Sstevel@tonic-gate  */
26607c478bd9Sstevel@tonic-gate       if(pstr->c == '\0')
26617c478bd9Sstevel@tonic-gate 	return 1;
26627c478bd9Sstevel@tonic-gate /*
26637c478bd9Sstevel@tonic-gate  * Using the wildcard to match successively longer sections of
26647c478bd9Sstevel@tonic-gate  * the remaining characters of the line, attempt to match
26657c478bd9Sstevel@tonic-gate  * the tail of the line against the tail of the pattern.
26667c478bd9Sstevel@tonic-gate  */
26677c478bd9Sstevel@tonic-gate       while(lstr->c) {
26687c478bd9Sstevel@tonic-gate 	GlhLineStream old_lstr = *lstr;
26697c478bd9Sstevel@tonic-gate 	GlhLineStream old_pstr = *pstr;
26707c478bd9Sstevel@tonic-gate 	if(glh_line_matches_glob(lstr, pstr))
26717c478bd9Sstevel@tonic-gate 	  return 1;
26727c478bd9Sstevel@tonic-gate /*
26737c478bd9Sstevel@tonic-gate  * Restore the line and pattern iterators for a new try.
26747c478bd9Sstevel@tonic-gate  */
26757c478bd9Sstevel@tonic-gate 	*lstr = old_lstr;
26767c478bd9Sstevel@tonic-gate 	*pstr = old_pstr;
26777c478bd9Sstevel@tonic-gate /*
26787c478bd9Sstevel@tonic-gate  * Prepare to try again, one character further into the line.
26797c478bd9Sstevel@tonic-gate  */
26807c478bd9Sstevel@tonic-gate 	glh_step_stream(lstr);
26817c478bd9Sstevel@tonic-gate       };
26827c478bd9Sstevel@tonic-gate       return 0; /* The pattern following the '*' didn't match */
26837c478bd9Sstevel@tonic-gate       break;
26847c478bd9Sstevel@tonic-gate /*
26857c478bd9Sstevel@tonic-gate  * A match-one-character wildcard operator.
26867c478bd9Sstevel@tonic-gate  */
26877c478bd9Sstevel@tonic-gate     case '?':
26887c478bd9Sstevel@tonic-gate /*
26897c478bd9Sstevel@tonic-gate  * If there is a character to be matched, skip it and advance the
26907c478bd9Sstevel@tonic-gate  * pattern pointer.
26917c478bd9Sstevel@tonic-gate  */
26927c478bd9Sstevel@tonic-gate       if(lstr->c) {
26937c478bd9Sstevel@tonic-gate 	glh_step_stream(lstr);
26947c478bd9Sstevel@tonic-gate 	glh_step_stream(pstr);
26957c478bd9Sstevel@tonic-gate /*
26967c478bd9Sstevel@tonic-gate  * If we hit the end of the line, there is no character
26977c478bd9Sstevel@tonic-gate  * matching the operator, so the pattern doesn't match.
26987c478bd9Sstevel@tonic-gate  */
26997c478bd9Sstevel@tonic-gate       } else {
27007c478bd9Sstevel@tonic-gate         return 0;
27017c478bd9Sstevel@tonic-gate       };
27027c478bd9Sstevel@tonic-gate       break;
27037c478bd9Sstevel@tonic-gate /*
27047c478bd9Sstevel@tonic-gate  * A character range operator, with the character ranges enclosed
27057c478bd9Sstevel@tonic-gate  * in matching square brackets.
27067c478bd9Sstevel@tonic-gate  */
27077c478bd9Sstevel@tonic-gate     case '[':
27087c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);  /* Skip the '[' character */
27097c478bd9Sstevel@tonic-gate       if(!lstr->c || !glh_matches_range(lstr->c, pstr))
27107c478bd9Sstevel@tonic-gate         return 0;
27117c478bd9Sstevel@tonic-gate       glh_step_stream(lstr);  /* Skip the character that matched */
27127c478bd9Sstevel@tonic-gate       break;
27137c478bd9Sstevel@tonic-gate /*
27147c478bd9Sstevel@tonic-gate  * A backslash in the pattern prevents the following character as
27157c478bd9Sstevel@tonic-gate  * being seen as a special character.
27167c478bd9Sstevel@tonic-gate  */
27177c478bd9Sstevel@tonic-gate     case '\\':
27187c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);  /* Skip the backslash */
27197c478bd9Sstevel@tonic-gate       /* Note fallthrough to default */
27207c478bd9Sstevel@tonic-gate /*
27217c478bd9Sstevel@tonic-gate  * A normal character to be matched explicitly.
27227c478bd9Sstevel@tonic-gate  */
2723cf421650SToomas Soome       /* FALLTHROUGH */
27247c478bd9Sstevel@tonic-gate     default:
27257c478bd9Sstevel@tonic-gate       if(lstr->c == pstr->c) {
27267c478bd9Sstevel@tonic-gate 	glh_step_stream(lstr);
27277c478bd9Sstevel@tonic-gate 	glh_step_stream(pstr);
27287c478bd9Sstevel@tonic-gate       } else {
27297c478bd9Sstevel@tonic-gate         return 0;
27307c478bd9Sstevel@tonic-gate       };
27317c478bd9Sstevel@tonic-gate       break;
27327c478bd9Sstevel@tonic-gate     };
27337c478bd9Sstevel@tonic-gate   };
27347c478bd9Sstevel@tonic-gate /*
27357c478bd9Sstevel@tonic-gate  * To get here, pattern must have been exhausted. The line only
27367c478bd9Sstevel@tonic-gate  * matches the pattern if the line as also been exhausted.
27377c478bd9Sstevel@tonic-gate  */
27387c478bd9Sstevel@tonic-gate   return pstr->c == '\0' && lstr->c == '\0';
27397c478bd9Sstevel@tonic-gate }
27407c478bd9Sstevel@tonic-gate 
27417c478bd9Sstevel@tonic-gate /*.......................................................................
27427c478bd9Sstevel@tonic-gate  * Match a character range expression terminated by an unescaped close
27437c478bd9Sstevel@tonic-gate  * square bracket.
27447c478bd9Sstevel@tonic-gate  *
27457c478bd9Sstevel@tonic-gate  * Input:
27467c478bd9Sstevel@tonic-gate  *  c              char    The character to be matched with the range
27477c478bd9Sstevel@tonic-gate  *                         pattern.
27487c478bd9Sstevel@tonic-gate  *  pstr  GlhLineStream *  The iterator stream being used to traverse
27497c478bd9Sstevel@tonic-gate  *                         the pattern.
27507c478bd9Sstevel@tonic-gate  * Output:
27517c478bd9Sstevel@tonic-gate  *  return          int    0 - Doesn't match.
27527c478bd9Sstevel@tonic-gate  *                         1 - The character matched.
27537c478bd9Sstevel@tonic-gate  */
glh_matches_range(char c,GlhLineStream * pstr)27547c478bd9Sstevel@tonic-gate static int glh_matches_range(char c, GlhLineStream *pstr)
27557c478bd9Sstevel@tonic-gate {
27567c478bd9Sstevel@tonic-gate   int invert = 0;              /* True to invert the sense of the match */
27577c478bd9Sstevel@tonic-gate   int matched = 0;             /* True if the character matched the pattern */
27587c478bd9Sstevel@tonic-gate   char lastc = '\0';           /* The previous character in the pattern */
27597c478bd9Sstevel@tonic-gate /*
27607c478bd9Sstevel@tonic-gate  * If the first character is a caret, the sense of the match is
27617c478bd9Sstevel@tonic-gate  * inverted and only if the character isn't one of those in the
27627c478bd9Sstevel@tonic-gate  * range, do we say that it matches.
27637c478bd9Sstevel@tonic-gate  */
27647c478bd9Sstevel@tonic-gate   if(pstr->c == '^') {
27657c478bd9Sstevel@tonic-gate     glh_step_stream(pstr);
27667c478bd9Sstevel@tonic-gate     invert = 1;
27677c478bd9Sstevel@tonic-gate   };
27687c478bd9Sstevel@tonic-gate /*
27697c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
27707c478bd9Sstevel@tonic-gate  * character of the range (not including the caret).
27717c478bd9Sstevel@tonic-gate  */
27727c478bd9Sstevel@tonic-gate   if(pstr->c == '-') {
27737c478bd9Sstevel@tonic-gate     glh_step_stream(pstr);
27747c478bd9Sstevel@tonic-gate     if(c == '-')
27757c478bd9Sstevel@tonic-gate       matched = 1;
27767c478bd9Sstevel@tonic-gate /*
27777c478bd9Sstevel@tonic-gate  * Skip other leading '-' characters since they make no sense.
27787c478bd9Sstevel@tonic-gate  */
27797c478bd9Sstevel@tonic-gate     while(pstr->c == '-')
27807c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);
27817c478bd9Sstevel@tonic-gate   };
27827c478bd9Sstevel@tonic-gate /*
27837c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
27847c478bd9Sstevel@tonic-gate  * character of the range (not including the caret or a hyphen).
27857c478bd9Sstevel@tonic-gate  */
27867c478bd9Sstevel@tonic-gate   if(pstr->c == ']') {
27877c478bd9Sstevel@tonic-gate     glh_step_stream(pstr);
27887c478bd9Sstevel@tonic-gate     if(c == ']')
27897c478bd9Sstevel@tonic-gate       matched = 1;
27907c478bd9Sstevel@tonic-gate   };
27917c478bd9Sstevel@tonic-gate /*
27927c478bd9Sstevel@tonic-gate  * Having dealt with the characters that have special meanings at
27937c478bd9Sstevel@tonic-gate  * the beginning of a character range expression, see if the
27947c478bd9Sstevel@tonic-gate  * character matches any of the remaining characters of the range,
27957c478bd9Sstevel@tonic-gate  * up until a terminating ']' character is seen.
27967c478bd9Sstevel@tonic-gate  */
27977c478bd9Sstevel@tonic-gate   while(!matched && pstr->c && pstr->c != ']') {
27987c478bd9Sstevel@tonic-gate /*
27997c478bd9Sstevel@tonic-gate  * Is this a range of characters signaled by the two end characters
28007c478bd9Sstevel@tonic-gate  * separated by a hyphen?
28017c478bd9Sstevel@tonic-gate  */
28027c478bd9Sstevel@tonic-gate     if(pstr->c == '-') {
28037c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);  /* Skip the hyphen */
28047c478bd9Sstevel@tonic-gate       if(pstr->c != ']') {
28057c478bd9Sstevel@tonic-gate         if(c >= lastc && c <= pstr->c)
28067c478bd9Sstevel@tonic-gate 	  matched = 1;
28077c478bd9Sstevel@tonic-gate       };
28087c478bd9Sstevel@tonic-gate /*
28097c478bd9Sstevel@tonic-gate  * A normal character to be compared directly.
28107c478bd9Sstevel@tonic-gate  */
28117c478bd9Sstevel@tonic-gate     } else if(pstr->c == c) {
28127c478bd9Sstevel@tonic-gate       matched = 1;
28137c478bd9Sstevel@tonic-gate     };
28147c478bd9Sstevel@tonic-gate /*
28157c478bd9Sstevel@tonic-gate  * Record and skip the character that we just processed.
28167c478bd9Sstevel@tonic-gate  */
28177c478bd9Sstevel@tonic-gate     lastc = pstr->c;
28187c478bd9Sstevel@tonic-gate     if(pstr->c != ']')
28197c478bd9Sstevel@tonic-gate       glh_step_stream(pstr);
28207c478bd9Sstevel@tonic-gate   };
28217c478bd9Sstevel@tonic-gate /*
28227c478bd9Sstevel@tonic-gate  * Find the terminating ']'.
28237c478bd9Sstevel@tonic-gate  */
28247c478bd9Sstevel@tonic-gate   while(pstr->c && pstr->c != ']')
28257c478bd9Sstevel@tonic-gate     glh_step_stream(pstr);
28267c478bd9Sstevel@tonic-gate /*
28277c478bd9Sstevel@tonic-gate  * Did we find a terminating ']'?
28287c478bd9Sstevel@tonic-gate  */
28297c478bd9Sstevel@tonic-gate   if(pstr->c == ']') {
28307c478bd9Sstevel@tonic-gate /*
28317c478bd9Sstevel@tonic-gate  * Skip the terminating ']'.
28327c478bd9Sstevel@tonic-gate  */
28337c478bd9Sstevel@tonic-gate     glh_step_stream(pstr);
28347c478bd9Sstevel@tonic-gate /*
28357c478bd9Sstevel@tonic-gate  * If the pattern started with a caret, invert the sense of the match.
28367c478bd9Sstevel@tonic-gate  */
28377c478bd9Sstevel@tonic-gate     if(invert)
28387c478bd9Sstevel@tonic-gate       matched = !matched;
28397c478bd9Sstevel@tonic-gate /*
28407c478bd9Sstevel@tonic-gate  * If the pattern didn't end with a ']', then it doesn't match,
28417c478bd9Sstevel@tonic-gate  * regardless of the value of the required sense of the match.
28427c478bd9Sstevel@tonic-gate  */
28437c478bd9Sstevel@tonic-gate   } else {
28447c478bd9Sstevel@tonic-gate     matched = 0;
28457c478bd9Sstevel@tonic-gate   };
28467c478bd9Sstevel@tonic-gate   return matched;
28477c478bd9Sstevel@tonic-gate }
28487c478bd9Sstevel@tonic-gate 
2849