/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
*
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* to whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
* OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
* INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Except as contained in this notice, the name of a copyright holder
* shall not be used in advertising or otherwise to promote the sale, use
* or other dealings in this Software without prior written authorization
* of the copyright holder.
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include "ioutil.h"
#include "history.h"
#include "freelist.h"
#include "errmsg.h"
/*
* History lines are split into sub-strings of GLH_SEG_SIZE
* characters. To avoid wasting space in the GlhLineSeg structure,
* this should be a multiple of the size of a pointer.
*/
/*
* GlhLineSeg structures contain fixed sized segments of a larger
* string. These are linked into lists to record strings, with all but
* the last segment having GLH_SEG_SIZE characters. The last segment
* of a string is terminated within the GLH_SEG_SIZE characters with a
* '\0'.
*/
struct GlhLineSeg {
char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */
/* substring of a line, as indicated by 'next' */
/* being NULL, is '\0' terminated. */
};
/*
* History lines are recorded in a hash table, such that repeated
* lines are stored just once.
*
* Start by defining the size of the hash table. This should be a
* prime number.
*/
/*
* Each history line will be represented in the hash table by a
* structure of the following type.
*/
struct GlhHashNode {
/* parent hash-table bucket. */
/* the time-ordered list of history lines. */
/* a line isn't reported redundantly. */
};
/*
* How many new GlhHashNode elements should be allocated at a time?
*/
/*
* All history lines which hash to a given bucket in the hash table, are
* recorded in a structure of the following type.
*/
struct GlhHashBucket {
};
size_t n);
size_t n);
typedef struct {
} GlhLineHash;
/*
* GlhLineNode's are used to record history lines in time order.
*/
struct GlhLineNode {
/* the line belongs. */
};
/*
* The number of GlhLineNode elements per freelist block.
*/
/*
* Encapsulate the time-ordered list of historical lines.
*/
typedef struct {
} GlhLineList;
/*
* The _glh_lookup_history() returns copies of history lines in a
* dynamically allocated array. This array is initially allocated
* GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be
* too small, realloc() is used to increase its size to the required
* size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to
* reduce the number of realloc() operations needed.
*/
/*
* Encapsulate all of the resources needed to store historical input lines.
*/
struct GlHistory {
/* into lists of sub-strings recording input lines. */
/* session is currently active. */
/* is being searched for. Note that if prefix==NULL */
/* and prefix_len>0, this means that no line in */
/* the buffer starts with the requested prefix. */
/* history lines */
/* currently being used to record sub-lines */
/* not currently being used to record sub-lines */
};
#ifndef WITHOUT_FILE_SYSTEM
#endif
size_t n);
/*
* The following structure and functions are used to iterate through
* the characters of a segmented history line.
*/
typedef struct {
/* be returned from. */
/* the next unread character. */
char c; /* The current character in the input line */
/*
* See if search prefix contains any globbing characters.
*/
/*
* Match a line against a search pattern.
*/
/*.......................................................................
* Create a line history maintenance object.
*
* Input:
* buflen size_t The number of bytes to allocate to the
* buffer that is used to record all of the
* most recent lines of user input that will fit.
* If buflen==0, no buffer will be allocated.
* Output:
* return GlHistory * The new object, or NULL on error.
*/
{
int i;
/*
* Allocate the container.
*/
if(!glh) {
return NULL;
};
/*
* Before attempting any operation that might fail, initialize the
* container at least up to the point at which it can safely be passed
* to _del_GlHistory().
*/
for(i=0; i<GLH_HASH_SIZE; i++)
/*
* Allocate a place to record error messages.
*/
return _del_GlHistory(glh);
/*
* Allocate the buffer, if required.
*/
return _del_GlHistory(glh);
};
/*
* All nodes of the buffer are currently unused, so link them all into
* a list and make glh->unused point to the head of this list.
*/
};
};
/*
* Allocate the GlhLineNode freelist.
*/
return _del_GlHistory(glh);
/*
* Allocate the GlhHashNode freelist.
*/
return _del_GlHistory(glh);
/*
* Allocate the array that _glh_lookup_history() uses to return a
* copy of a given history line. This will be resized when necessary.
*/
return _del_GlHistory(glh);
};
return glh;
}
/*.......................................................................
* Delete a GlHistory object.
*
* Input:
* glh GlHistory * The object to be deleted.
* Output:
* return GlHistory * The deleted object (always NULL).
*/
{
if(glh) {
/*
* Delete the error-message buffer.
*/
/*
* Delete the buffer.
*/
};
/*
* Delete the freelist of GlhLineNode's.
*/
/*
* The contents of the list were deleted by deleting the freelist.
*/
/*
* Delete the freelist of GlhHashNode's.
*/
/*
* Delete the lookup buffer.
*/
/*
* Delete the container.
*/
};
return NULL;
}
/*.......................................................................
* Append a new line to the history list, deleting old lines to make
* room, if needed.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The line to be archived.
* force int Unless this flag is non-zero, empty lines aren't
* archived. This flag requests that the line be
* archived regardless.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
int i;
/*
* Check the arguments.
*/
return 1;
};
/*
* Is history enabled?
*/
return 0;
/*
* Cancel any ongoing search.
*/
if(_glh_cancel_search(glh))
return 1;
/*
* How long is the string to be recorded, being careful not to include
* any terminating '\n' character.
*/
if(nlptr)
else
/*
* Is the line empty?
*/
empty = 1;
/*
* If the line is empty, don't add it to the buffer unless explicitly
* told to.
*/
return 0;
/*
* Has an upper limit to the number of lines in the history list been
* specified?
*/
/*
* If necessary, remove old lines until there is room to add one new
* line without exceeding the specified line limit.
*/
/*
* We can't archive the line if the maximum number of lines allowed is
* zero.
*/
return 0;
};
/*
* Unless already stored, store a copy of the line in the history buffer,
* then return a reference-counted hash-node pointer to this copy.
*/
if(!hnode) {
return 1;
};
/*
* Allocate a new node in the time-ordered list of lines.
*/
/*
* If a new line-node couldn't be allocated, discard our copy of the
* stored line before reporting the error.
*/
if(!lnode) {
return 1;
};
/*
* Record a pointer to the hash-table record of the line in the new
* list node.
*/
/*
* Append the new node to the end of the time-ordered list.
*/
else
/*
* Record the addition of a line to the list.
*/
return 0;
}
/*.......................................................................
* Recall the next oldest line that has the search prefix last recorded
* by _glh_search_prefix().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, if anything
* was found, its contents will have been replaced
* with the matching line.
* dim size_t The allocated dimension of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
{
/*
* Check the arguments.
*/
if(glh)
return NULL;
};
/*
* Is history enabled?
*/
return NULL;
/*
* Check the line dimensions.
*/
return NULL;
};
/*
* Preserve the input line if needed.
*/
return NULL;
/*
* From where should we start the search?
*/
} else {
};
/*
* Search backwards through the list for the first match with the
* prefix string that differs from the last line that was recalled.
*/
/*
* Was a matching line found?
*/
if(node) {
/*
* Recall the found node as the starting point for subsequent
* searches.
*/
/*
* Copy the matching line into the provided line buffer.
*/
/*
* Return it.
*/
return line;
};
/*
* No match was found.
*/
return NULL;
}
/*.......................................................................
* Recall the next newest line that has the search prefix last recorded
* by _glh_search_prefix().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, if anything
* was found, its contents will have been replaced
* with the matching line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * The line requested, or NULL if no matching line
* was found.
*/
{
/*
* Check the arguments.
*/
if(glh)
return NULL;
};
/*
* Is history enabled?
*/
return NULL;
/*
* Check the line dimensions.
*/
return NULL;
};
/*
* From where should we start the search?
*/
} else {
return NULL;
};
/*
* Search forwards through the list for the first match with the
* prefix string.
*/
/*
* Was a matching line found?
*/
if(node) {
/*
* Copy the matching line into the provided line buffer.
*/
/*
* Record the starting point of the next search.
*/
/*
* If we just returned the line that was being entered when the search
* session first started, cancel the search.
*/
/*
* Return the matching line to the user.
*/
return line;
};
/*
* No match was found.
*/
return NULL;
}
/*.......................................................................
* If a search is in progress, cancel it.
*
* This involves discarding the line that was temporarily saved by
* _glh_find_backwards() when the search was originally started,
* and reseting the search iteration pointer to NULL.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
/*
* Check the arguments.
*/
if(!glh) {
return 1;
};
/*
* If there wasn't a search in progress, do nothing.
*/
return 0;
/*
* Reset the search pointers. Note that it is essential to set
* glh->recall to NULL before calling _glh_discard_line(), to avoid an
* infinite recursion.
*/
/*
* Delete the node of the preserved line.
*/
return 0;
}
/*.......................................................................
* Set the prefix of subsequent history searches.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line const char * The command line who's prefix is to be used.
* prefix_len int The length of the prefix.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
/*
* Check the arguments.
*/
if(!glh) {
return 1;
};
/*
* Is history enabled?
*/
return 0;
/*
* Discard any existing prefix.
*/
/*
* Only store a copy of the prefix string if it isn't a zero-length string.
*/
if(prefix_len > 0) {
/*
* Get a reference-counted copy of the prefix from the history cache buffer.
*/
/*
* Was there insufficient buffer space?
*/
return 1;
};
};
return 0;
}
/*.......................................................................
* Recall the oldest recorded line.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the oldest line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
{
/*
* Check the arguments.
*/
if(glh)
return NULL;
};
/*
* Is history enabled?
*/
return NULL;
/*
* Check the line dimensions.
*/
return NULL;
};
/*
* Preserve the input line if needed.
*/
return NULL;
/*
* Locate the oldest line that belongs to the current group.
*/
;
/*
* No line found?
*/
if(!node)
return NULL;
/*
* Record the above node as the starting point for subsequent
* searches.
*/
/*
* Copy the recalled line into the provided line buffer.
*/
/*
* If we just returned the line that was being entered when the search
* session first started, cancel the search.
*/
return line;
}
/*.......................................................................
* Recall the line that was being entered when the search started.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the line that was
* being entered when the search was started.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
{
/*
* Check the arguments.
*/
if(glh)
return NULL;
};
/*
* If history isn't enabled, or no history search has yet been started,
* ignore the call.
*/
return NULL;
/*
* Check the line dimensions.
*/
return NULL;
};
/*
* Copy the recalled line into the provided line buffer.
*/
/*
* Since we have returned to the starting point of the search, cancel it.
*/
return line;
}
/*.......................................................................
* Query the id of a history line offset by a given number of lines from
* the one that is currently being recalled. If a recall session isn't
* in progress, or the offset points outside the history list, 0 is
* returned.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* offset int The line offset (0 for the current line, < 0
* for an older line, > 0 for a newer line.
* Output:
* return GlhLineID The identifier of the line that is currently
* being recalled, or 0 if no recall session is
* currently in progress.
*/
{
/*
* Is history enabled?
*/
return 0;
/*
* Search forward 'offset' lines to find the required line.
*/
if(offset >= 0) {
offset--;
};
} else {
offset++;
};
};
}
/*.......................................................................
* Recall a line by its history buffer ID. If the line is no longer
* in the buffer, or the id is zero, NULL is returned.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlhLineID The ID of the line to be returned.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the saved line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
{
/*
* Is history enabled?
*/
return NULL;
/*
* Preserve the input line if needed.
*/
return NULL;
/*
* Search for the specified line.
*/
/*
* Not found?
*/
return NULL;
/*
* Record the node of the matching line as the starting point
* for subsequent searches.
*/
/*
* Copy the recalled line into the provided line buffer.
*/
return line;
}
/*.......................................................................
* Save the current history in a specified file.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* filename const char * The name of the new file to record the
* history in.
* comment const char * Extra information such as timestamps will
* be recorded on a line started with this
* string, the idea being that the file can
* double as a command file. Specify "" if
* you don't care.
* max_lines int The maximum number of lines to save, or -1
* to save all of the lines in the history
* list.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int max_lines)
{
#ifdef WITHOUT_FILE_SYSTEM
return 1;
#else
/*
* Check the arguments.
*/
if(glh)
return 1;
};
/*
* Attempt to open the specified file.
*/
if(!fp)
/*
* If a ceiling on the number of lines to save was specified, count
* that number of lines backwards, to find the first line to be saved.
*/
if(max_lines >= 0) {
;
};
if(!head)
/*
* Write the contents of the history buffer to the history file, writing
* associated data such as timestamps, to a line starting with the
* specified comment string.
*/
/*
* Write peripheral information associated with the line, as a comment.
*/
};
/*
* Write the history line.
*/
};
};
/*
* Close the history file.
*/
return 0;
#endif
}
#ifndef WITHOUT_FILE_SYSTEM
/*.......................................................................
* This is a private error return function of _glh_save_history(). It
* composes an error report in the error buffer, composed using
* sprintf("%s %s (%s)", message, filename, strerror(errno)). It then
* closes fp and returns the error return code of _glh_save_history().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* message const char * A message to be followed by the filename.
* filename const char * The name of the offending output file.
* fp FILE * The stream to be closed (send NULL if not
* open).
* Output:
* return int Always 1.
*/
{
if(fp)
return 1;
}
/*.......................................................................
* Write a timestamp to a given stdio stream, in the format
* yyyymmddhhmmss
*
* Input:
* fp FILE * The stream to write to.
* timestamp time_t The timestamp to be written.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
struct tm *t; /* THe broken-down calendar time */
/*
* Get the calendar components corresponding to the given timestamp.
*/
return 1;
return 0;
};
/*
* Write the calendar time as yyyymmddhhmmss.
*/
return 1;
return 0;
}
#endif
/*.......................................................................
* Restore previous history lines from a given file.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* filename const char * The name of the file to read from.
* comment const char * The same comment string that was passed to
* _glh_save_history() when this file was
* written.
* line char * A buffer into which lines can be read.
* dim size_t The allocated dimension of line[].
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
#ifdef WITHOUT_FILE_SYSTEM
return 1;
#else
unsigned group; /* The identifier of the history group to which */
/* the line belongs. */
int lineno; /* The line number being read */
/*
* Check the arguments.
*/
if(glh)
return 1;
};
/*
* Measure the length of the comment string.
*/
/*
* Clear the history list.
*/
/*
* Attempt to open the specified file. Don't treat it as an error
* if the file doesn't exist.
*/
if(!fp)
return 0;
/*
* Attempt to read each line and preceding peripheral info, and add these
* to the history list.
*/
char *lptr; /* A pointer into the input line */
/*
* Check that the line starts with the comment string.
*/
"Corrupt history parameter line", fp);
};
/*
* Skip spaces and tabs after the comment.
*/
;
/*
* The next word must be a timestamp.
*/
"Corrupt timestamp", fp);
};
/*
* Skip spaces and tabs.
*/
lptr++;
/*
* The next word must be an unsigned integer group number.
*/
"Corrupt group id", fp);
};
/*
* Skip spaces and tabs.
*/
lptr++;
/*
* There shouldn't be anything left on the line.
*/
if(*lptr != '\n') {
"Corrupt parameter line", fp);
};
/*
* Now read the history line itself.
*/
lineno++;
/*
* Append the line to the history buffer.
*/
"Insufficient memory to record line", fp);
};
/*
* Record the group and timestamp information along with the line.
*/
};
};
/*
* Close the file.
*/
return 0;
#endif
}
#ifndef WITHOUT_FILE_SYSTEM
/*.......................................................................
* This is a private error return function of _glh_load_history().
*/
{
/*
* Convert the line number to a string.
*/
/*
* Render an error message.
*/
/*
* Close the file.
*/
if(fp)
return 1;
}
/*.......................................................................
* Read a timestamp from a string.
*
* Input:
* string char * The string to read from.
* endp char ** On output *endp will point to the next unprocessed
* character in string[].
* timestamp time_t * The timestamp will be assigned to *t.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
struct tm t;
/*
* There are 14 characters in the date format yyyymmddhhmmss.
*/
/*
* If the time wasn't available at the time that the line was recorded
* it will have been written as "?". Check for this before trying
* to read the timestamp.
*/
if(string[0] == '\?') {
*timestamp = -1;
return 0;
};
/*
* The timestamp is expected to be written in the form yyyymmddhhmmss.
*/
return 1;
};
/*
* Copy the timestamp out of the string.
*/
/*
* Decode the timestamp.
*/
&sec) != 6) {
return 1;
};
/*
* Advance the string pointer over the successfully read timestamp.
*/
/*
* Copy the read values into a struct tm.
*/
t.tm_wday = 0;
t.tm_yday = 0;
t.tm_isdst = -1;
/*
* Convert the contents of the struct tm to a time_t.
*/
return 0;
}
#endif
/*.......................................................................
* Switch history groups.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* group unsigned The new group identifier. This will be recorded
* with subsequent history lines, and subsequent
* history searches will only return lines with
* this group identifier. This allows multiple
* separate history lists to exist within
* a single GlHistory object. Note that the
* default group identifier is 0.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
/*
* Check the arguments.
*/
if(!glh) {
if(glh)
return 1;
};
/*
* Is the group being changed?
*/
/*
* Cancel any ongoing search.
*/
if(_glh_cancel_search(glh))
return 1;
/*
* Record the new group.
*/
};
return 0;
}
/*.......................................................................
* Query the current history group.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return unsigned The group identifier.
*/
{
}
/*.......................................................................
* Display the contents of the history list.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* write_fn GlWriteFn * The function to call to write the line, or
* 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* fmt const char * A format string. This can contain arbitrary
* characters, which are written verbatim, plus
* any of the following format directives:
* %D - The date, like 2001-11-20
* %T - The time of day, like 23:59:59
* %N - The sequential entry number of the
* line in the history buffer.
* %G - The history group number of the line.
* %% - A literal % character.
* %H - The history line.
* all_groups int If true, display history lines from all
* history groups. Otherwise only display
* those of the current history group.
* max_lines int If max_lines is < 0, all available lines
* are displayed. Otherwise only the most
* recent max_lines lines will be displayed.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
/*
* Check the arguments.
*/
if(glh)
return 1;
};
/*
* Is history enabled?
*/
return 0;
/*
* Work out the length to display ID numbers, choosing the length of
* the biggest number in the buffer. Smaller numbers will be padded
* with leading zeroes if needed.
*/
/*
* Find the largest group number.
*/
grpmax = 0;
};
/*
* Find out how many characters are needed to display the group number.
*/
/*
* Find the node that follows the oldest line to be displayed.
*/
if(max_lines < 0) {
} else if(max_lines==0) {
return 0;
} else {
break;
};
/*
* If the number of lines in the buffer doesn't exceed the specified
* maximum, start from the oldest line in the buffer.
*/
if(!oldest)
};
/*
* List the history lines in increasing time order.
*/
/*
* Only display lines from the current history group, unless
* told otherwise.
*/
/*
* Work out the calendar representation of the node timestamp.
*/
/*
* Parse the format string.
*/
while(*fptr) {
/*
* Search for the start of the next format directive or the end of the string.
*/
fptr++;
/*
* Display any literal characters that precede the located directive.
*/
return 1;
};
/*
* Did we hit a new directive before the end of the line?
*/
if(*fptr) {
/*
* Obey the directive. Ignore unknown directives.
*/
switch(*++fptr) {
case 'D': /* Display the date */
return 1;
};
break;
case 'T': /* Display the time of day */
return 1;
};
break;
case 'N': /* Display the sequential entry number */
return 1;
break;
case 'G':
return 1;
break;
case 'H': /* Display the history line */
return 1;
};
break;
case '%': /* A literal % symbol */
return 1;
break;
};
/*
* Skip the directive.
*/
if(*fptr)
fptr++;
};
};
};
};
return 0;
}
/*.......................................................................
* Change the size of the history buffer.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* bufsize size_t The number of bytes in the history buffer, or 0
* to delete the buffer completely.
* Output:
* return int 0 - OK.
* 1 - Insufficient memory (the previous buffer
* will have been retained). No error message
* will be displayed.
*/
{
int i;
/*
* Check the arguments.
*/
if(!glh) {
return 1;
};
/*
* How many buffer segments does the requested buffer size correspond
* to?
*/
/*
* Has a different size than the current size been requested?
*/
/*
* Cancel any ongoing search.
*/
(void) _glh_cancel_search(glh);
/*
* Create a wholly new buffer?
*/
return 1;
/*
* Link the currently unused nodes of the buffer into a list.
*/
};
/*
* Delete an existing buffer?
*/
} else if(nbuff == 0) {
/*
* Change from one finite buffer size to another?
*/
} else {
/*
* Starting from the oldest line in the buffer, discard lines until
* the buffer contains at most 'nbuff' used line segments.
*/
/*
* Attempt to allocate a new buffer.
*/
if(!buffer) {
return 1;
};
/*
* Copy the used segments of the old buffer to the start of the new buffer.
*/
nbusy = 0;
for(i=0; i<GLH_HASH_SIZE; i++) {
nbusy++;
};
};
};
/*
* Make a list of the new buffer's unused segments.
*/
if(i < nbuff)
/*
* Discard the old buffer.
*/
/*
* Install the new buffer.
*/
};
};
return 0;
}
/*.......................................................................
* Set an upper limit to the number of lines that can be recorded in the
* history list, or remove a previously specified limit.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* max_lines int The maximum number of lines to allow, or -1 to
* cancel a previous limit and allow as many lines
* as will fit in the current history buffer size.
*/
{
if(!glh)
return;
/*
* Apply a new limit?
*/
/*
* Count successively older lines until we reach the start of the
* list, or until we have seen max_lines lines (at which point 'node'
* will be line number max_lines+1).
*/
int nline = 0;
;
/*
* Discard any lines that exceed the limit.
*/
if(node) {
/*
* Delete nodes from the head of the list until we reach the node that
* is to be kept.
*/
};
};
/*
* Record the new limit.
*/
return;
}
/*.......................................................................
* Discard either all history, or the history associated with the current
* history group.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* all_groups int If true, clear all of the history. If false,
* clear only the stored lines associated with the
* currently selected history group.
*/
{
int i;
/*
* Check the arguments.
*/
if(!glh)
return;
/*
* Cancel any ongoing search.
*/
(void) _glh_cancel_search(glh);
/*
* Delete all history lines regardless of group?
*/
if(all_groups) {
/*
* Claer the time-ordered list of lines.
*/
/*
* Clear the hash table.
*/
for(i=0; i<GLH_HASH_SIZE; i++)
/*
* Move all line segment nodes back onto the list of unused segments.
*/
};
} else {
};
/*
* Just delete lines of the current group?
*/
} else {
/*
* Search out and delete the line nodes of the current group.
*/
/*
* Keep a record of the following node before we delete the current
* node.
*/
/*
* Discard this node?
*/
};
};
return;
}
/*.......................................................................
* Temporarily enable or disable the history list.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* enable int If true, turn on the history mechanism. If
* false, disable it.
*/
{
if(glh)
}
/*.......................................................................
* Discard a given archived input line.
*
* Input:
* glh GlHistory * The history container object.
* node GlhLineNode * The line to be discarded, specified via its
* entry in the time-ordered list of historical
* input lines.
*/
{
/*
* Remove the node from the linked list.
*/
else
else
/*
* If we are deleting the node that is marked as the start point of the
* last ID search, remove the cached starting point.
*/
/*
* If we are deleting the node that is marked as the start point of the
* next prefix search, cancel the search.
*/
/*
* Delete our copy of the line.
*/
/*
* Return the node to the freelist.
*/
/*
* Record the removal of a line from the list.
*/
return;
}
/*.......................................................................
* Lookup the details of a given history line, given its id.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlLineID The sequential number of the line.
* line const char ** A pointer to a copy of the history line will be
* assigned to *line. Beware that this pointer may
* be invalidated by the next call to any public
* history function.
* group unsigned * The group membership of the line will be assigned
* to *group.
* timestamp time_t * The timestamp of the line will be assigned to
* *timestamp.
* Output:
* return int 0 - The requested line wasn't found.
* 1 - The line was found.
*/
{
/*
* Check the arguments.
*/
if(!glh)
return 0;
/*
* Search for the line that has the specified ID.
*/
/*
* Not found?
*/
if(!node)
return 0;
/*
* Has the history line been requested?
*/
if(line) {
/*
* If necessary, reallocate the lookup buffer to accomodate the size of
* a copy of the located line.
*/
if(!lbuf) {
return 0;
};
};
/*
* Copy the history line into the lookup buffer.
*/
/*
* Assign the lookup buffer as the returned line pointer.
*/
};
/*
* Does the caller want to know the group of the line?
*/
if(group)
/*
* Does the caller want to know the timestamp of the line?
*/
if(timestamp)
return 1;
}
/*.......................................................................
* Lookup a node in the history list by its ID.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlhLineID The ID of the line to be returned.
* Output:
* return GlhLIneNode * The located node, or NULL if not found.
*/
{
/*
* Is history enabled?
*/
return NULL;
/*
* If possible, start at the end point of the last ID search.
* Otherwise start from the head of the list.
*/
if(!node)
/*
* Search forwards from 'node'?
*/
/*
* Search backwards from 'node'?
*/
} else {
};
/*
* Return the located node (this will be NULL if the ID wasn't found).
*/
return node;
}
/*.......................................................................
* pointers can be specified as NULL.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* enabled int * If history is enabled, *enabled will be
* set to 1. Otherwise it will be assigned 0.
* group unsigned * The current history group ID will be assigned
* to *group.
* max_lines int * The currently requested limit on the number
* of history lines in the list, or -1 if
* unlimited.
*/
int *max_lines)
{
if(glh) {
if(enabled)
if(group)
if(max_lines)
};
}
/*.......................................................................
* Get the range of lines in the history buffer.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* oldest unsigned long * The sequential entry number of the oldest
* line in the history list will be assigned
* to *oldest, unless there are no lines, in
* which case 0 will be assigned.
* newest unsigned long * The sequential entry number of the newest
* line in the history list will be assigned
* to *newest, unless there are no lines, in
* which case 0 will be assigned.
* nlines int * The number of lines currently in the history
* list.
*/
{
if(glh) {
if(oldest)
if(newest)
if(nlines)
};
}
/*.......................................................................
* Return the size of the history buffer and the amount of the
* buffer that is currently in use.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* buff_size size_t * The size of the history buffer (bytes).
* buff_used size_t * The amount of the history buffer that
* is currently occupied (bytes).
*/
{
if(glh) {
if(buff_size)
/*
* Determine the amount of buffer space that is currently occupied.
*/
if(buff_used)
};
}
/*.......................................................................
* Return extra information (ie. in addition to that provided by errno)
* about the last error to occur in any of the public functions of this
* module.
*
* Input:
* glh GlHistory * The container of the history list.
* Output:
* return const char * A pointer to the internal buffer in which
* the error message is temporarily stored.
*/
{
}
/*.......................................................................
* Unless already stored, store a copy of the line in the history buffer,
* then return a reference-counted hash-node pointer to this copy.
*
* Input:
* glh GlHistory * The history maintenance buffer.
* line const char * The history line to be recorded.
* n size_t The length of the string, excluding any '\0'
* terminator.
* Output:
* return GlhHashNode * The hash-node containing the stored line, or
* NULL on error.
*/
size_t n)
{
int i;
/*
* In which bucket should the line be recorded?
*/
/*
* Is the line already recorded there?
*/
/*
* If the line isn't recorded in the buffer yet, make room for it.
*/
if(!hnode) {
/*
* How many string segments will be needed to record the new line,
* including space for a '\0' terminator?
*/
/*
* Discard the oldest history lines in the buffer until at least
* 'nseg' segments have been freed up, or until we run out of buffer
* space.
*/
/*
* If the buffer is smaller than the new line, don't attempt to truncate
* it to fit. Simply don't archive it.
*/
return NULL;
/*
* Record the line in the first 'nseg' segments of the list of unused segments.
*/
offset = 0;
/*
* Create a new hash-node for the line.
*/
if(!hnode)
return NULL;
/*
* Move the copy of the line from the list of unused segments to
* the hash node.
*/
/*
* Prepend the new hash node to the list within the associated bucket.
*/
/*
* Initialize the rest of the members of the hash node.
*/
};
/*
* Increment the reference count of the line.
*/
return hnode;
}
/*.......................................................................
* Decrement the reference count of the history line of a given hash-node,
* and if the count reaches zero, delete both the hash-node and the
* buffered copy of the line.
*
* Input:
* glh GlHistory * The history container object.
* hnode GlhHashNode * The node to be removed.
* Output:
* return GlhHashNode * The deleted hash-node (ie. NULL).
*/
{
if(hnode) {
/*
* If decrementing the reference count of the hash-node doesn't reduce
* the reference count to zero, then the line is still in use in another
* object, so don't delete it yet. Return NULL to indicate that the caller's
* access to the hash-node copy has been deleted.
*/
return NULL;
/*
* Remove the hash-node from the list in its parent bucket.
*/
} else {
;
if(prev)
};
/*
* Return the line segments of the hash-node to the list of unused segments.
*/
/*
* Get the last node of the list of line segments referenced in the hash-node,
* while counting the number of line segments used.
*/
;
/*
* Prepend the list of line segments used by the hash node to the
* list of unused line segments.
*/
};
/*
* Return the container of the hash-node to the freelist.
*/
};
return NULL;
}
/*.......................................................................
* Private function to locate the hash bucket associated with a given
* history line.
*
* This uses a hash-function described in the dragon-book
* ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and
* Ullman; pub. Adison Wesley) page 435.
*
* Input:
* glh GlHistory * The history container object.
* line const char * The historical line to look up.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return GlhHashBucket * The located hash-bucket.
*/
size_t n)
{
unsigned long h = 0L;
int i;
for(i=0; i<n; i++) {
unsigned char c = line[i];
h = 65599UL * h + c; /* 65599 is a prime close to 2^16 */
};
}
/*.......................................................................
* Find a given history line within a given hash-table bucket.
*
* Input:
* bucket GlhHashBucket * The hash-table bucket in which to search.
* line const char * The historical line to lookup.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return GlhHashNode * The hash-table entry of the line, or NULL
* if not found.
*/
size_t n)
{
/*
* Compare each of the lines in the list of lines, against 'line'.
*/
return node;
};
return NULL;
}
/*.......................................................................
* Return non-zero if a given string is equal to a given segmented line
* node.
*
* Input:
* hash GlhHashNode * The hash-table entry of the line.
* line const char * The string to be compared to the segmented
* line.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return int 0 - The lines differ.
* 1 - The lines are the same.
*/
{
int i;
/*
* Do the two lines have the same length?
*/
return 0;
/*
* Compare the characters of the segmented and unsegmented versions
* of the line.
*/
const char *s = seg->s;
for(i=0; n>0 && i<GLH_SEG_SIZE; i++,n--) {
if(*line++ != *s++)
return 0;
};
};
return 1;
}
/*.......................................................................
* Return non-zero if a given line has the specified segmented search
* prefix.
*
* Input:
* line GlhHashNode * The line to be compared against the prefix.
* prefix GlhHashNode * The search prefix, or NULL to match any string.
* Output:
* return int 0 - The line doesn't have the specified prefix.
* 1 - The line has the specified prefix.
*/
{
/*
* When prefix==NULL, this means that the nul string
* is to be matched, and this matches all lines.
*/
if(!prefix)
return 1;
/*
* Wrap the two history lines that are to be compared in iterator
* stream objects.
*/
/*
* If the prefix contains a glob pattern, match the prefix as a glob
* pattern.
*/
if(glh_contains_glob(prefix))
/*
* Is the prefix longer than the line being compared against it?
*/
return 0;
/*
* Compare the line to the prefix.
*/
};
/*
* Did we reach the end of the prefix string before finding
* any differences?
*/
return pstr.c == '\0';
}
/*.......................................................................
* Copy a given history line into a specified output string.
*
* Input:
* hash GlhHashNode The hash-table entry of the history line to
* be copied.
* line char * A copy of the history line.
* dim size_t The allocated dimension of the line buffer.
*/
{
int i;
const char *s = seg->s;
*line++ = *s++;
};
/*
* If the line wouldn't fit in the output buffer, replace the last character
* with a '\0' terminator.
*/
if(dim==0)
}
/*.......................................................................
* This function should be called whenever a new line recall is
* attempted. It preserves a copy of the current input line in the
* history list while other lines in the history list are being
* returned.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The current contents of the input line buffer.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
{
/*
* If a recall session has already been started, but we have returned
* to the preserved copy of the input line, if the user has changed
* this line, we should replace the preserved copy of the original
* input line with the new one. To do this simply cancel the session,
* so that a new session is started below.
*/
};
/*
* If this is the first line recall of a new recall session, save the
* current line for potential recall later, and mark it as the last
* line recalled.
*/
return 1;
/*
* The above call to _glh_add_history() will have incremented the line
* sequence number, after adding the line. Since we only want this to
* to be incremented for permanently entered lines, decrement it again.
*/
};
return 0;
}
/*.......................................................................
* Return non-zero if a history search session is currently in progress.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return int 0 - No search is currently in progress.
* 1 - A search is in progress.
*/
{
}
/*.......................................................................
* Initialize a character iterator object to point to the start of a
* given history line. The first character of the line will be placed
* in str->c, and subsequent characters can be placed there by calling
* glh_strep_stream().
*
* Input:
* str GlhLineStream * The iterator object to be initialized.
* line GlhHashNode * The history line to be iterated over (a
* NULL value here, is interpretted as an
* empty string by glh_step_stream()).
*/
{
}
/*.......................................................................
* Copy the next unread character in the line being iterated, in str->c.
* Once the end of the history line has been reached, all futher calls
* set str->c to '\0'.
*
* Input:
* str GlhLineStream * The history-line iterator to read from.
*/
{
/*
* Get the character from the current iterator position within the line.
*/
/*
* Unless we have reached the end of the string, move the iterator
* to the position of the next character in the line.
*/
};
}
/*.......................................................................
* Return non-zero if the specified search prefix contains any glob
* wildcard characters.
*
* Input:
* prefix GlhHashNode * The search prefix.
* Output:
* return int 0 - The prefix doesn't contain any globbing
* characters.
* 1 - The prefix contains at least one
* globbing character.
*/
{
/*
* Wrap a stream iterator around the prefix, so that we can traverse it
* without worrying about line-segmentation.
*/
/*
* Search for unescaped wildcard characters.
*/
while(pstr.c != '\0') {
switch(pstr.c) {
case '\\': /* Skip escaped characters */
break;
case '*': case '?': case '[': /* A wildcard character? */
return 1;
break;
};
};
/*
* No wildcard characters were found.
*/
return 0;
}
/*.......................................................................
* Return non-zero if the history line matches a search prefix containing
* a glob pattern.
*
* Input:
* lstr GlhLineStream * The iterator stream being used to traverse
* the history line that is being matched.
* pstr GlhLineStream * The iterator stream being used to traverse
* the pattern.
* Output:
* return int 0 - Doesn't match.
* 1 - The line matches the pattern.
*/
{
/*
* Match each character of the pattern until we reach the end of the
* pattern.
*/
while(pstr->c != '\0') {
/*
* Handle the next character of the pattern.
*/
switch(pstr->c) {
/*
* A match zero-or-more characters wildcard operator.
*/
case '*':
/*
* Skip the '*' character in the pattern.
*/
/*
* If the pattern ends with the '*' wildcard, then the
* rest of the line matches this.
*/
if(pstr->c == '\0')
return 1;
/*
* Using the wildcard to match successively longer sections of
* the remaining characters of the line, attempt to match
* the tail of the line against the tail of the pattern.
*/
while(lstr->c) {
return 1;
/*
* Restore the line and pattern iterators for a new try.
*/
/*
* Prepare to try again, one character further into the line.
*/
};
return 0; /* The pattern following the '*' didn't match */
break;
/*
* A match-one-character wildcard operator.
*/
case '?':
/*
* If there is a character to be matched, skip it and advance the
* pattern pointer.
*/
if(lstr->c) {
/*
* If we hit the end of the line, there is no character
* matching the operator, so the pattern doesn't match.
*/
} else {
return 0;
};
break;
/*
* A character range operator, with the character ranges enclosed
* in matching square brackets.
*/
case '[':
return 0;
break;
/*
* A backslash in the pattern prevents the following character as
* being seen as a special character.
*/
case '\\':
/* Note fallthrough to default */
/*
* A normal character to be matched explicitly.
*/
default:
} else {
return 0;
};
break;
};
};
/*
* To get here, pattern must have been exhausted. The line only
* matches the pattern if the line as also been exhausted.
*/
}
/*.......................................................................
* Match a character range expression terminated by an unescaped close
* square bracket.
*
* Input:
* c char The character to be matched with the range
* pattern.
* pstr GlhLineStream * The iterator stream being used to traverse
* the pattern.
* Output:
* return int 0 - Doesn't match.
* 1 - The character matched.
*/
{
/*
* If the first character is a caret, the sense of the match is
* inverted and only if the character isn't one of those in the
* range, do we say that it matches.
*/
if(pstr->c == '^') {
invert = 1;
};
/*
* The hyphen is only a special character when it follows the first
* character of the range (not including the caret).
*/
if(pstr->c == '-') {
if(c == '-')
matched = 1;
/*
* Skip other leading '-' characters since they make no sense.
*/
while(pstr->c == '-')
};
/*
* The hyphen is only a special character when it follows the first
* character of the range (not including the caret or a hyphen).
*/
if(pstr->c == ']') {
if(c == ']')
matched = 1;
};
/*
* Having dealt with the characters that have special meanings at
* the beginning of a character range expression, see if the
* character matches any of the remaining characters of the range,
* up until a terminating ']' character is seen.
*/
/*
* Is this a range of characters signaled by the two end characters
* separated by a hyphen?
*/
if(pstr->c == '-') {
if(pstr->c != ']') {
matched = 1;
};
/*
* A normal character to be compared directly.
*/
} else if(pstr->c == c) {
matched = 1;
};
/*
* Record and skip the character that we just processed.
*/
if(pstr->c != ']')
};
/*
* Find the terminating ']'.
*/
/*
* Did we find a terminating ']'?
*/
if(pstr->c == ']') {
/*
* Skip the terminating ']'.
*/
/*
* If the pattern started with a caret, invert the sense of the match.
*/
if(invert)
/*
* If the pattern didn't end with a ']', then it doesn't match,
* regardless of the value of the required sense of the match.
*/
} else {
matched = 0;
};
return matched;
}