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