committed by
Holger Vogt
2 changed files with 970 additions and 0 deletions
-
915src/hist_info.c
-
55src/hist_info.h
@ -0,0 +1,915 @@ |
|||
/* Functions for managing history of strings, such as commands |
|||
* |
|||
* Implemented using circular buffers for both the storage of the |
|||
* strings and their locating information. |
|||
*/ |
|||
|
|||
#include <limits.h> |
|||
#include <stdbool.h> |
|||
#include <stddef.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
#include "hist_info.h" |
|||
|
|||
|
|||
/* Structure locating string informaton */ |
|||
struct Str_info { |
|||
unsigned int n_byte_sz; /* length of string with NULL at end */ |
|||
char *sz; /* Address of string */ |
|||
}; |
|||
struct History_info { |
|||
struct History_info_opt hi_opt; |
|||
bool f_first_resize_check_done; |
|||
unsigned int n_str_cur; /* current number of strings */ |
|||
unsigned int n_str_alloc; /* allocated size of array */ |
|||
unsigned int n_insert_since_resize_check; |
|||
/* For buffer size management. After this many more |
|||
* string insertions, the buffer will be reduced |
|||
* in size if it is excessively large for the data */ |
|||
size_t n_byte_buf_cur; /* Current amount of history buffer in use */ |
|||
size_t n_byte_buf_alloc; /* Allocated size of history buffer */ |
|||
unsigned int index_str_start; /* Index of first history string item */ |
|||
unsigned int index_str_cur; /* Index of next string */ |
|||
unsigned int index_str_to_return; /* Index of string to return */ |
|||
char *p_char_buf; /* Address of char buffer for history strings */ |
|||
char *p_char_buf_start; /* Start of data in buffer */ |
|||
char *p_char_buf_cur; /* Current free address in buffer */ |
|||
char *p_char_buf_end; /* Byte past last address in buffer */ |
|||
|
|||
/* Array of n_max items locating commnads. This is a circular buffer |
|||
* with the data elements being from index_start to index_end-1, |
|||
* inclusive of the endpoints with wrapping considered */ |
|||
struct Str_info p_str_info[1]; |
|||
|
|||
/* Buffer for string history strings follows p_str_info array */ |
|||
}; /* end of struct History_info */ |
|||
|
|||
|
|||
static int adjust_history_options(struct History_info_opt *p_hi_opt, |
|||
bool f_is_init); |
|||
static struct History_info *history_alloc( |
|||
unsigned int n_str_max, size_t n_byte_char_buf); |
|||
static int history_copy(struct History_info *p_hi_dst, |
|||
const struct History_info *p_hi_src); |
|||
static const char *history_get_prev1(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str, bool f_update_pos); |
|||
static void history_make_empty(struct History_info *p_hi); |
|||
static int history_resize(struct History_info **pp_hi, |
|||
unsigned int n_str_alloc, size_t n_byte_char_buf_alloc); |
|||
static inline const char *return_str_data(struct History_info *p_hi, |
|||
unsigned int index_str_to_return, unsigned int *p_n_char_str); |
|||
|
|||
/* This function allocates and initializes history information |
|||
* |
|||
* Parameter |
|||
* n_max: Maximum number of history items to store |
|||
* |
|||
* Return values |
|||
* NULL on error; otherwise an initialized history structure. Options |
|||
* are modified to actual values used. */ |
|||
struct History_info *history_init(struct History_info_opt *p_hi_opt) |
|||
{ |
|||
struct History_info *p_hi; |
|||
|
|||
/* Make history options valid */ |
|||
if (adjust_history_options(p_hi_opt, true) < 0) { |
|||
return (struct History_info *) NULL; |
|||
} |
|||
|
|||
/* Do allocation */ |
|||
if ((p_hi = history_alloc(p_hi_opt->n_str_init, |
|||
p_hi_opt->n_byte_str_buf_init)) == |
|||
(struct History_info *) NULL) { |
|||
return (struct History_info *) NULL; |
|||
} |
|||
|
|||
/* Save options */ |
|||
p_hi->hi_opt = *p_hi_opt; |
|||
|
|||
p_hi->f_first_resize_check_done = false; |
|||
|
|||
return p_hi; |
|||
} /* end of function history_init */ |
|||
|
|||
|
|||
|
|||
/* This function modifies history options to valid values |
|||
* |
|||
* Return codes |
|||
* +1: Modified OK |
|||
* 0: No changes required |
|||
* -1: Unknown option structure size */ |
|||
static int adjust_history_options(struct History_info_opt *p_hi_opt, |
|||
bool f_is_init) |
|||
{ |
|||
/* Validate and adjust arguments */ |
|||
if (p_hi_opt->n_byte_struct != sizeof(struct History_info_opt)) { |
|||
/* Unknown version */ |
|||
return -1; |
|||
} |
|||
|
|||
int xrc = 0; |
|||
|
|||
/* Must be at least 2 strings buffered */ |
|||
if (p_hi_opt->n_str_init < 2) { |
|||
if (f_is_init) { |
|||
xrc = +1; |
|||
} |
|||
p_hi_opt->n_str_init = 2; |
|||
} |
|||
|
|||
/* If initialization, max # strings buffered must be at least init |
|||
* size */ |
|||
if (f_is_init) { |
|||
if (p_hi_opt->n_str_max < p_hi_opt->n_str_init) { |
|||
xrc = +1; |
|||
p_hi_opt->n_str_max = p_hi_opt->n_str_init; |
|||
} |
|||
} |
|||
|
|||
/* Initial string buffer must be at least 2 bytes */ |
|||
|
|||
if (p_hi_opt->n_byte_str_buf_init < 2) { |
|||
if (f_is_init) { |
|||
xrc = +1; |
|||
} |
|||
p_hi_opt->n_byte_str_buf_init = 2; |
|||
} |
|||
|
|||
/* Oversize factor must be at least 4 */ |
|||
if (p_hi_opt->oversize_factor < 4) { |
|||
xrc = +1; |
|||
p_hi_opt->oversize_factor = 4; |
|||
} |
|||
|
|||
return xrc; |
|||
} /* end of function adjust_history_options */ |
|||
|
|||
|
|||
|
|||
/* This function allocates history information |
|||
* |
|||
* Parameters |
|||
* n_max: Maximum number of history items to store |
|||
* n_byte_buf: Character buffer size in bytes |
|||
* |
|||
* Return values |
|||
* NULL on allocation failure; |
|||
* otherwise an initialized history structure */ |
|||
static struct History_info *history_alloc( |
|||
unsigned int n_str, size_t n_byte_char_buf) |
|||
{ |
|||
struct History_info *p_hi; |
|||
|
|||
/* Memory offset to history buffer from start of structure |
|||
* Note that no alignment is required for char */ |
|||
const size_t offset_char_buf = sizeof(struct History_info) + |
|||
sizeof(struct Str_info) * (n_str - 1); |
|||
|
|||
/* Total allocation size */ |
|||
const size_t n_byte_alloc = offset_char_buf + n_byte_char_buf; |
|||
|
|||
/* Allocate history buffer */ |
|||
if ((p_hi = (struct History_info *) malloc(n_byte_alloc)) == |
|||
(struct History_info *) NULL) { |
|||
return (struct History_info *) NULL; |
|||
} |
|||
|
|||
p_hi->n_str_alloc = n_str; |
|||
p_hi->n_insert_since_resize_check = 0; |
|||
p_hi->n_byte_buf_alloc = n_byte_char_buf; |
|||
|
|||
{ |
|||
/* Locate start and end of string buffer */ |
|||
char *p_cur = (char *) p_hi + offset_char_buf; |
|||
p_hi->p_char_buf = p_cur; |
|||
p_cur += n_byte_char_buf; |
|||
p_hi->p_char_buf_end = p_cur; |
|||
} |
|||
|
|||
/* Initialize the buffer and locator array to empty. */ |
|||
history_make_empty(p_hi); |
|||
|
|||
return p_hi; |
|||
} /* end of function history_alloc */ |
|||
|
|||
|
|||
|
|||
/* This function copies one history state into another. It is useful when |
|||
* a history buffer needs to be resized or the number of items in the |
|||
* commnad history is changed. The source history info must have at least |
|||
* one string. |
|||
* |
|||
* Parameters |
|||
* p_hi_dst: Address of destination history info |
|||
* p_hi_src: Address of source history info |
|||
* |
|||
* Return codes |
|||
* +1: Destination buffer is too small. Data was truncated. |
|||
* 0: Data copied OK. |
|||
* |
|||
* Note: if there are more history strings in the source structure |
|||
* than the n_str_max field in the destinnation, only the newest strings |
|||
* are copied. |
|||
*/ |
|||
static int history_copy(struct History_info *p_hi_dst, |
|||
const struct History_info *p_hi_src) |
|||
{ |
|||
int xrc = 0; |
|||
|
|||
const unsigned int n_str_src = p_hi_src->n_str_cur; /* # src strs avail */ |
|||
|
|||
/* Copy data directly taken from source */ |
|||
p_hi_dst->hi_opt = p_hi_src->hi_opt; /* Copy options */ |
|||
p_hi_dst->f_first_resize_check_done = |
|||
p_hi_src->f_first_resize_check_done; |
|||
p_hi_dst->n_insert_since_resize_check = |
|||
p_hi_src->n_insert_since_resize_check; |
|||
|
|||
/* If the source is empty, set dest to empty */ |
|||
if (n_str_src == 0) { |
|||
history_make_empty(p_hi_dst); |
|||
return 0; |
|||
} |
|||
|
|||
const unsigned int n_str_src_alloc = p_hi_src->n_str_alloc; /* size */ |
|||
unsigned int index_cur_src; /* current index to copy */ |
|||
unsigned int index_str_start_src; /* index of oldest item in source |
|||
* to be copied */ |
|||
|
|||
/* Find the first source index to copy using index_cur_src as a temp |
|||
* variable */ |
|||
if (n_str_src > p_hi_dst->n_str_alloc) { |
|||
/* If there are more source strings than the number of available |
|||
* entries in the destination, only copy the newest items. May have |
|||
* to handle a wrap to the start of the buffer. */ |
|||
if ((index_str_start_src = p_hi_src->index_str_start + |
|||
(n_str_src - p_hi_dst->n_str_alloc)) >= n_str_src_alloc) { |
|||
index_str_start_src -= n_str_src_alloc; |
|||
} |
|||
xrc = +1; /* indicate truncation */ |
|||
} |
|||
else { |
|||
/* Enough elements in destination for all strings, so start at the |
|||
* beginning */ |
|||
index_str_start_src = p_hi_src->index_str_start; |
|||
} |
|||
|
|||
/* Now make index_cur_src equal to the newest source history item */ |
|||
if ((index_cur_src = p_hi_src->index_str_cur - 1) == (unsigned int) -1) { |
|||
/* Underflow occurred, so overflow to correct */ |
|||
index_cur_src += p_hi_src->n_str_alloc; /* Set to end */ |
|||
} |
|||
|
|||
/* Start adding strings at bottom of Str_info buffer */ |
|||
unsigned int index_cur_dst = p_hi_dst->n_str_alloc - 1; |
|||
|
|||
/* Start writing strings at botttom of char buffer. This location is |
|||
* actually the address of the top of the buffer unrolled to the byte |
|||
* past its end. */ |
|||
char *p_dst_cur = p_hi_dst->p_char_buf_end; |
|||
const char *p_char_buf_dst = p_hi_dst->p_char_buf; |
|||
|
|||
size_t n_byte_buf_cur_dst = 0; /* bytes copied to destination */ |
|||
const struct Str_info * const p_str_info_src0 = p_hi_src->p_str_info; |
|||
struct Str_info * const p_str_info_dst0 = p_hi_dst->p_str_info; |
|||
|
|||
/* Copy each element in the source string history starting with the |
|||
* newest element so that if truncation occurs, it will be the oldest |
|||
* strings that get truncated */ |
|||
for ( ; ; ) { |
|||
/* Locate data to copy */ |
|||
const struct Str_info * const p_str_info_src = |
|||
p_str_info_src0 + index_cur_src; |
|||
const unsigned int n_byte_str_cur = p_str_info_src->n_byte_sz; |
|||
struct Str_info * const p_str_info_dst = p_str_info_dst0 + |
|||
index_cur_dst; |
|||
|
|||
/* Locate the destination in the char * buffer */ |
|||
if ((p_dst_cur -= n_byte_str_cur) < p_char_buf_dst) { |
|||
break; /* Data will not fit */ |
|||
} |
|||
|
|||
/* Copy the data */ |
|||
(void) memcpy(p_dst_cur, p_str_info_src->sz, n_byte_str_cur); |
|||
n_byte_buf_cur_dst += n_byte_str_cur; |
|||
|
|||
/* Save locating information */ |
|||
p_str_info_dst->sz = p_dst_cur; |
|||
p_str_info_dst->n_byte_sz = n_byte_str_cur; |
|||
|
|||
/* Test for exit, which is completion of copy of string at |
|||
* index_str_src. On exit, index_cur_dst is at last string |
|||
* copied (which is the oldest string copied) */ |
|||
if (index_cur_src == index_str_start_src) { |
|||
break; |
|||
} |
|||
|
|||
/* Move to next indices */ |
|||
if (--index_cur_src == (unsigned int) -1) { |
|||
index_cur_src += n_str_src_alloc; |
|||
} |
|||
--index_cur_dst; /* by construction will not wrap */ |
|||
} /* end of loop copying strings */ |
|||
|
|||
|
|||
/* Complete the information for the copied history info */ |
|||
p_hi_dst->n_str_cur = p_hi_dst->n_str_alloc - index_cur_dst; |
|||
p_hi_dst->n_byte_buf_cur = n_byte_buf_cur_dst; |
|||
p_hi_dst->index_str_start = index_cur_dst; /* last copied is oldest */ |
|||
p_hi_dst->index_str_cur = 0; /* next item wraps to begin at top of buf */ |
|||
p_hi_dst->p_char_buf_start = p_dst_cur; /* oldest string (last copied) */ |
|||
p_hi_dst->p_char_buf_cur = p_hi_dst->p_char_buf; |
|||
/* top of char buf is next free byte */ |
|||
|
|||
/* Set the index to return for history_get_next and history_get_prev. |
|||
* If the earlier command no longer exists, set to the closest value */ |
|||
{ |
|||
/* Look in the source history information to see the location |
|||
* relative to the current insert position */ |
|||
const unsigned int index_str_to_return = |
|||
p_hi_src->index_str_to_return; |
|||
if (index_str_to_return == UINT_MAX) { |
|||
p_hi_dst->index_str_to_return = UINT_MAX; |
|||
} |
|||
else { |
|||
const unsigned index_str_start = p_hi_src->index_str_start; |
|||
const unsigned int index_str_cur = p_hi_src->index_str_cur; |
|||
|
|||
/* "Unrolled" positions -- as if wrapped part extended past |
|||
* end of buffer */ |
|||
const unsigned index_str_cur_unrolled = |
|||
index_str_cur > index_str_start ? index_str_cur : |
|||
index_str_cur + p_hi_src->n_str_alloc; |
|||
const unsigned int index_str_to_return_unrolled = |
|||
index_str_to_return > index_str_start ? |
|||
index_str_to_return : |
|||
index_str_to_return + p_hi_src->n_str_alloc; |
|||
|
|||
/* From unrolled positions, offset is always a simple |
|||
* subtraction. With the offset being from the position to |
|||
* return, the offset is always nonnegative */ |
|||
const unsigned int offset_from_cur = index_str_cur_unrolled - |
|||
index_str_to_return_unrolled; |
|||
|
|||
/* The offset must be reduced if the command being indexed was |
|||
* not copied due to truncation */ |
|||
const unsigned int offset_from_cur_dst = |
|||
offset_from_cur <= p_hi_dst->n_str_cur ? |
|||
offset_from_cur : |
|||
p_hi_dst->n_str_cur; |
|||
|
|||
/* Locate the index for the string to return */ |
|||
if (p_hi_dst->index_str_cur >= offset_from_cur_dst) { |
|||
/* No buffer wrap */ |
|||
p_hi_dst->index_str_to_return = |
|||
p_hi_dst->index_str_cur - offset_from_cur_dst; |
|||
} |
|||
else { /* Wrap */ |
|||
p_hi_dst->index_str_to_return = |
|||
p_hi_dst->index_str_cur + |
|||
(p_hi_dst->n_str_alloc - offset_from_cur_dst); |
|||
} |
|||
} |
|||
} /* end of block locating index for returned history item */ |
|||
|
|||
return xrc; |
|||
} /* end of function history_copy */ |
|||
|
|||
|
|||
|
|||
/* Set values to make an empty history buffer */ |
|||
static void history_make_empty(struct History_info *p_hi) |
|||
{ |
|||
p_hi->n_str_cur = 0; |
|||
p_hi->n_byte_buf_cur = 0; |
|||
p_hi->index_str_start = 0; |
|||
p_hi->index_str_cur = 0; |
|||
p_hi->index_str_to_return = UINT_MAX; |
|||
p_hi->p_char_buf_start = p_hi->p_char_buf_cur = p_hi->p_char_buf; |
|||
} /* end of function history_copy_empty */ |
|||
|
|||
|
|||
|
|||
/* This function returns the previous history item |
|||
* For the previous item, the value returned should be one step |
|||
* behind. That is, the first "previous" string is the current |
|||
* string. It is done because there is a new current string not yet |
|||
* in the history buffer when the previous one is being requested. |
|||
* |
|||
* Cases |
|||
* Empty buffer -- n_str_cur == 0 |
|||
* Return empty string |
|||
* Buffer not empty |
|||
* Decrement index_str_to_return. If < index_str_start, |
|||
* set to index_str_cur - 1 |
|||
*/ |
|||
const char *history_get_prev(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str) |
|||
{ |
|||
return history_get_prev1(p_hi, p_n_char_str, true); |
|||
} /* end of function history_get_prev */ |
|||
|
|||
|
|||
|
|||
/* Worker function for history_get_prev and history_get_last that provides |
|||
* the option to not change the current position */ |
|||
static const char *history_get_prev1(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str, bool f_update_pos) |
|||
{ |
|||
const unsigned int n_str = p_hi->n_str_cur; |
|||
|
|||
/* Handle caase of empty history info */ |
|||
if (n_str == 0) { |
|||
/* Set size if buffer given */ |
|||
if (p_n_char_str != (unsigned *) NULL) { |
|||
*p_n_char_str = 0; |
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
unsigned int index_str_to_return = f_update_pos && |
|||
p_hi->index_str_to_return != UINT_MAX ? |
|||
p_hi->index_str_to_return : p_hi->index_str_cur; |
|||
|
|||
if (n_str == p_hi->n_str_alloc) { /* Full buffer */ |
|||
if (index_str_to_return == 0) { /* must wrap */ |
|||
index_str_to_return = n_str - 1; |
|||
} |
|||
else { /* wrap not required */ |
|||
--index_str_to_return; |
|||
} |
|||
} |
|||
else { /* Partial buffer */ |
|||
if (index_str_to_return == 0) { /* must wrap */ |
|||
if (p_hi->index_str_start < p_hi->index_str_cur) { |
|||
/* Data in [start, end-1] */ |
|||
index_str_to_return = p_hi->index_str_cur - 1; |
|||
} |
|||
else { /* end less than start (if ==, buffer is full) */ |
|||
/* Data in [start, n_str_max-1]. If end != 0, there |
|||
* is a second piece of data in [0, end-1] */ |
|||
index_str_to_return = p_hi->n_str_alloc - 1; |
|||
} |
|||
} /* end of case of wrap at 0 */ |
|||
else { /* current index_str_to_return > 0 */ |
|||
if (index_str_to_return == p_hi->index_str_start) { |
|||
if (p_hi->index_str_cur == 0) { |
|||
/* last str at bottom of buf */ |
|||
index_str_to_return = p_hi->n_str_alloc - 1; |
|||
} |
|||
else { |
|||
index_str_to_return = p_hi->index_str_cur - 1; |
|||
} |
|||
} |
|||
else { /* no special cases, so just decrement */ |
|||
--index_str_to_return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* Save updated position */ |
|||
if (f_update_pos) { |
|||
p_hi->index_str_to_return = index_str_to_return; |
|||
} |
|||
|
|||
/* Return the string data */ |
|||
return return_str_data(p_hi, index_str_to_return, p_n_char_str); |
|||
} /* end of function history_get_prev */ |
|||
|
|||
|
|||
|
|||
/* This function returns the newest history item, that is the last one |
|||
* added. It can be used to decide whether or not to add a string. For |
|||
* example, duplicate consecutive strings can be suppressed. */ |
|||
const char *history_get_newest(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str) |
|||
{ |
|||
return history_get_prev1(p_hi, p_n_char_str, false); |
|||
} /* end of function history_get_cur */ |
|||
|
|||
|
|||
|
|||
/* This function returns the next history item */ |
|||
const char *history_get_next(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str) |
|||
{ |
|||
const unsigned int n_str = p_hi->n_str_cur; |
|||
|
|||
/* Handle caase of empty history info */ |
|||
if (n_str == 0) { |
|||
/* Set size if buffer given */ |
|||
if (p_n_char_str != (unsigned *) NULL) { |
|||
*p_n_char_str = 0; |
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
|
|||
unsigned int index_str_to_return = p_hi->index_str_to_return; |
|||
if (index_str_to_return == UINT_MAX) { |
|||
/* next item requested before any prevous ones were */ |
|||
index_str_to_return = p_hi->index_str_start; |
|||
} |
|||
else { |
|||
if (n_str == p_hi->n_str_alloc) { /* Full buffer */ |
|||
if (index_str_to_return == n_str - 1) { /* must wrap */ |
|||
index_str_to_return = 0; |
|||
} |
|||
else { /* wrap not required */ |
|||
++index_str_to_return; |
|||
} |
|||
} |
|||
else { /* Partial buffer */ |
|||
if (index_str_to_return == p_hi->n_str_alloc - 1) { |
|||
/* Must wrap */ |
|||
if (p_hi->index_str_start < p_hi->index_str_cur) { |
|||
/* Data in [start, end-1] */ |
|||
index_str_to_return = p_hi->index_str_start; |
|||
} |
|||
else { /* end less than start (if ==, buffer is full) */ |
|||
/* Data in [start, n_str_max-1]. If end != 0, there |
|||
* is a second piece of data in [0, end-1] */ |
|||
if (p_hi->index_str_cur == 0) { |
|||
index_str_to_return = p_hi->index_str_start; |
|||
} |
|||
else { |
|||
index_str_to_return = 0; |
|||
} |
|||
} |
|||
} /* end of case of wrap at 0 */ |
|||
else { /* current index_str_to_return < max buf index */ |
|||
if (index_str_to_return == p_hi->index_str_cur - 1) { |
|||
/* not at end */ |
|||
index_str_to_return = p_hi->index_str_start; |
|||
} |
|||
else { /* no special cases, so just increment */ |
|||
++index_str_to_return; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* Save updated position */ |
|||
p_hi->index_str_to_return = index_str_to_return; |
|||
|
|||
/* Return the string data */ |
|||
return return_str_data(p_hi, index_str_to_return, p_n_char_str); |
|||
} /* end of function history_get_next */ |
|||
|
|||
|
|||
|
|||
/* This function returns the history information according to the given |
|||
* index */ |
|||
static inline const char *return_str_data(struct History_info *p_hi, |
|||
unsigned int index_str_to_return, unsigned int *p_n_char_str) |
|||
{ |
|||
struct Str_info *p_str_info_cur = p_hi->p_str_info + index_str_to_return; |
|||
/* Return the string. Also return size if a buffer was given */ |
|||
if (p_n_char_str != (unsigned *) NULL) { |
|||
/* -1 because value stored includes NULL at end */ |
|||
*p_n_char_str = p_str_info_cur->n_byte_sz - 1; |
|||
} |
|||
|
|||
return p_str_info_cur->sz; |
|||
} /* end of function return_str_data */ |
|||
|
|||
|
|||
|
|||
/* This function copies one history state into another. It is useful when |
|||
* a history buffer needs to be resized or the number of items in the |
|||
* commnad history is changed. The source history info must have at least |
|||
* one string. |
|||
* |
|||
* Parameters |
|||
* p_hi_dst: Address of destination history info |
|||
* p_hi_src: Address of source history info |
|||
* |
|||
* Return codes |
|||
* +1: Destination buffer is too small. Data was truncated. |
|||
* 0: Buffer resized OK |
|||
* -1: Allocation failure. Buffer same as when input |
|||
* |
|||
* Note: if there are more history strings in the source structure |
|||
* than the n_str_max field in the destinnation, only the newest strings |
|||
* are copied. |
|||
*/ |
|||
static int history_resize(struct History_info **pp_hi, |
|||
unsigned int n_str_alloc, size_t n_byte_char_buf_alloc) |
|||
{ |
|||
struct History_info *p_hi_old = *pp_hi; |
|||
struct History_info *p_hi_new; |
|||
|
|||
/* Allocate a new history info of the desired sizes */ |
|||
if ((p_hi_new = history_alloc(n_str_alloc, n_byte_char_buf_alloc)) == |
|||
(struct History_info *) NULL) { |
|||
return -1; |
|||
} |
|||
|
|||
/* Copy the old history into the new one */ |
|||
const int xrc = history_copy(p_hi_new, p_hi_old); |
|||
|
|||
/* Free the old allocation */ |
|||
history_free(p_hi_old); |
|||
|
|||
*pp_hi = p_hi_new; /* return new info */ |
|||
|
|||
return xrc; |
|||
} /* end of function history_resize */ |
|||
|
|||
|
|||
|
|||
/* This function frees menory used by a History_info struct */ |
|||
void history_free(struct History_info *p_hi) |
|||
{ |
|||
if (p_hi != (struct History_info *) NULL) { |
|||
free((void *) p_hi); |
|||
} |
|||
|
|||
return; |
|||
} /* end of function history_free */ |
|||
|
|||
|
|||
|
|||
/* This function adds the string str, of length n_char_str, excluding any |
|||
* terminating null, which is optional. The history info always adds a |
|||
* terminating null to the stored data. |
|||
* |
|||
* Return codes |
|||
* 0: Added OK |
|||
* -1: Unable to add. |
|||
* |
|||
* Remarks |
|||
* The History_info structure may be allocated again to obtain more buffer |
|||
* space. Failure of this allocation would result in a -1 return code. |
|||
*/ |
|||
int history_add(struct History_info **pp_hi, |
|||
unsigned int n_char_str, const char *str) |
|||
{ |
|||
const unsigned int n_byte_data = n_char_str + 1; /* with NULL */ |
|||
struct History_info *p_hi = *pp_hi; /* access history data */ |
|||
char *p_dst = (char *) NULL; /* Location to write new data */ |
|||
bool f_have_room; /* flag that there is room for the string */ |
|||
|
|||
/* If the buffer is full of strings, resize, doubling up to the maximum |
|||
* allowed size, and if that is not large enough, remove the oldest |
|||
* one to make room for this one */ |
|||
if (p_hi->n_str_cur == p_hi->n_str_alloc) { |
|||
unsigned int n_str_alloc_new = 2 * p_hi->n_str_alloc; |
|||
if (n_str_alloc_new > p_hi->hi_opt.n_str_max) { |
|||
n_str_alloc_new = p_hi->hi_opt.n_str_max; |
|||
} |
|||
|
|||
/* If the buffer can be made larger, try to do so */ |
|||
if (n_str_alloc_new > p_hi->n_str_alloc) { |
|||
if (history_resize(&p_hi, n_str_alloc_new, |
|||
p_hi->n_byte_buf_alloc) != 0) { |
|||
return -1; |
|||
} |
|||
*pp_hi = p_hi; /* point to new structure */ |
|||
f_have_room = true; |
|||
} |
|||
else { /* at max size already */ |
|||
f_have_room = false; |
|||
} |
|||
} |
|||
else { /* Allocated size not full yet */ |
|||
f_have_room = true; |
|||
} |
|||
|
|||
|
|||
/* If there is not room for the string, remove refrerence to the |
|||
* oldest one to make room */ |
|||
if (!f_have_room) { |
|||
p_hi->n_byte_buf_cur -= |
|||
p_hi->p_str_info[p_hi->index_str_start].n_byte_sz; |
|||
if (p_hi->index_str_start == p_hi->n_str_alloc - 1) { |
|||
p_hi->index_str_start = 0; |
|||
} |
|||
else { |
|||
++p_hi->index_str_start; |
|||
} |
|||
|
|||
/* Locate start of used buffer at the new starting string */ |
|||
p_hi->p_char_buf_start = |
|||
p_hi->p_str_info[p_hi->index_str_start].sz; |
|||
--p_hi->n_str_cur; |
|||
} |
|||
|
|||
|
|||
/* Try fitting the string in the free area. If that fails, the character |
|||
* buffer will be enlarged via a new history information structure. */ |
|||
{ |
|||
/* Identify free area of buffer |
|||
* (1) [cur, end) + [top, start) |
|||
* (2) [cur, start) |
|||
* (3) none |
|||
*/ |
|||
ptrdiff_t case_id = p_hi->p_char_buf_cur - p_hi->p_char_buf_start; |
|||
if (case_id > 0 || case_id == 0 && p_hi->n_str_cur == 0) { |
|||
/* Case 1, including an empty buffer. |
|||
* Try fitting the string from the free address to the end of the |
|||
* the buffer. If that fails, try fitting from the start of the |
|||
* buffer to the start of data, exclusive of start of data. */ |
|||
if (p_hi->p_char_buf_cur + n_byte_data <= p_hi->p_char_buf_end) { |
|||
p_dst = p_hi->p_char_buf_cur; |
|||
} |
|||
else { /* whould not fit at end so try at top */ |
|||
if (p_hi->p_char_buf + n_byte_data <= p_hi->p_char_buf_start) { |
|||
p_dst = p_hi->p_char_buf; |
|||
} |
|||
} |
|||
} |
|||
else if (case_id < 0) { /* Case 2 */ |
|||
/* Try fitting the string from the free address to the start |
|||
* of data, exclusive of start of data. */ |
|||
if (p_hi->p_char_buf_cur + n_byte_data <= p_hi->p_char_buf_start) { |
|||
p_dst = p_hi->p_char_buf_cur; |
|||
} |
|||
} |
|||
/* Else case 3: buffer is full, so cannot fit */ |
|||
} |
|||
|
|||
/* If the string would not fit, enlarge the char buffer and add */ |
|||
if (p_dst == (char *) NULL) { |
|||
if (history_resize(&p_hi, p_hi->n_str_alloc, |
|||
2 * p_hi->n_byte_buf_alloc + n_byte_data) != 0) { |
|||
return -1; /* could not resize */ |
|||
} |
|||
*pp_hi = p_hi; /* Update returned structure */ |
|||
|
|||
/* Buffer was enlarged enough to guarantee fitting, so can add |
|||
* without any checks */ |
|||
p_dst = p_hi->p_char_buf_cur; |
|||
} /* end of case of resize */ |
|||
|
|||
/* Add an entry into Str_info for this string */ |
|||
{ |
|||
struct Str_info *p_str_info = p_hi->p_str_info + p_hi->index_str_cur; |
|||
p_str_info->sz = p_dst; |
|||
p_str_info->n_byte_sz = n_byte_data; |
|||
} |
|||
|
|||
/* Set index to next free entry in Str_info */ |
|||
if (++p_hi->index_str_cur == p_hi->n_str_alloc) { |
|||
p_hi->index_str_cur = 0; |
|||
} |
|||
|
|||
/* Flag the item to return as not set */ |
|||
p_hi->index_str_to_return = UINT_MAX; |
|||
|
|||
/* Update data size */ |
|||
p_hi->n_byte_buf_cur += n_byte_data; |
|||
|
|||
/* Save string data in char buffer at p_dst */ |
|||
(void) memcpy(p_dst, str, n_char_str); |
|||
p_dst += n_char_str; |
|||
*p_dst++ = '\0'; /* null terminate and point to byte after null */ |
|||
|
|||
/* Update free location in char buffer, pointed to by p_dst after |
|||
* writing the string */ |
|||
p_hi->p_char_buf_cur = p_dst; |
|||
|
|||
++p_hi->n_str_cur; /* Increment the string count */ |
|||
|
|||
/* Test if the buffer should be shrunk. Using >= instead of == |
|||
* due to possibility that history_setopt changed the threshold |
|||
* to a smaller value */ |
|||
{ |
|||
bool f_do_check = false; |
|||
unsigned int n_insert_since_check = |
|||
++p_hi->n_insert_since_resize_check; |
|||
|
|||
/* Test if need to check for oversize based on options */ |
|||
if (p_hi->f_first_resize_check_done) { /* after 1st */ |
|||
const unsigned int n = |
|||
p_hi->hi_opt.n_insert_per_oversize_check; |
|||
/* Test for nonzero (supressing resizes) and threshold met */ |
|||
if (n != 0 && n_insert_since_check >= n) { |
|||
f_do_check = true; |
|||
} |
|||
} |
|||
else { /* first check */ |
|||
const unsigned int n = |
|||
p_hi->hi_opt.n_insert_first_oversize_check; |
|||
/* Test for nonzero (supressing resizes) and threshold met */ |
|||
if (n != 0 && n_insert_since_check >= n) { |
|||
f_do_check = true; |
|||
p_hi->f_first_resize_check_done = true; |
|||
} |
|||
} |
|||
|
|||
/* Do check if enough inserts have occurred */ |
|||
if (f_do_check) { |
|||
size_t n_byte_buf_alloc = p_hi->n_byte_buf_alloc; |
|||
p_hi->n_insert_since_resize_check = 0; |
|||
if (n_byte_buf_alloc > 4 && |
|||
p_hi->n_byte_buf_cur * p_hi->hi_opt.oversize_factor < |
|||
n_byte_buf_alloc) { |
|||
/* Buffer too large for existing data, so shrink it. |
|||
* Should that fail for some reason, simply continue with the |
|||
* existing buffer */ |
|||
(void) history_resize(&p_hi, p_hi->n_str_alloc, |
|||
n_byte_buf_alloc / 2); |
|||
*pp_hi = p_hi; /* point to new structure */ |
|||
} |
|||
} |
|||
} /* end of block for oversized buffer actions */ |
|||
|
|||
return 0; |
|||
} /* end of function history_add */ |
|||
|
|||
|
|||
|
|||
/* This function resets the returned buffer pointer so that the |
|||
* last command is returned by history_get_prev() */ |
|||
void history_reset_pos(struct History_info *p_hi) |
|||
{ |
|||
p_hi->index_str_to_return = UINT_MAX; |
|||
} /* end of function history_reset_pos */ |
|||
|
|||
|
|||
|
|||
/* This function sets history options after history has been initialized. |
|||
* An initialized History_info_opt structure is passed. The values of |
|||
* the structure will be modified to the closest allowable values if |
|||
* the ones given cannot be used. |
|||
* |
|||
* The values for n_str_init and n_byte_str_buf_init are ignored. |
|||
* The value for n_insert_first_oversize_check is also ignored if the |
|||
* first check has already been performed. |
|||
* |
|||
* Return codes |
|||
* +2: Unknown structure size. No action taken |
|||
* +1: Option values were modified but changes were made OK |
|||
* 0: Changes made OK |
|||
* -1: Unable to make change(s) |
|||
*/ |
|||
int history_setopt(struct History_info **pp_hi, |
|||
struct History_info_opt *p_hi_opt) |
|||
{ |
|||
int rc_adj; |
|||
|
|||
/* Adjust options */ |
|||
if ((rc_adj = adjust_history_options(p_hi_opt, 0)) < 0) { |
|||
/* Unknown version based on size of structure */ |
|||
return +2; |
|||
} |
|||
|
|||
struct History_info *p_hi = *pp_hi; /* access struct */ |
|||
|
|||
/* If the maximum number of items to log is being reduced below the |
|||
* current allocated value, a copy of the buffer with the new array |
|||
* size must be made. */ |
|||
{ |
|||
unsigned int n_str_max_new = p_hi_opt->n_str_max; |
|||
if (p_hi->n_str_alloc > n_str_max_new) { |
|||
if (history_resize(&p_hi, n_str_max_new, |
|||
p_hi->n_byte_buf_alloc) < 0) { |
|||
return -1; |
|||
} |
|||
|
|||
/* Override the maximum number of strings */ |
|||
p_hi->hi_opt.n_str_max = n_str_max_new; |
|||
|
|||
*pp_hi = p_hi; /* Point to new structure */ |
|||
} |
|||
} |
|||
|
|||
/* Other option changes will work as the are processed */ |
|||
{ |
|||
struct History_info_opt *p_hi_opt_dst = &p_hi->hi_opt; |
|||
p_hi_opt_dst->n_str_max = p_hi_opt->n_str_max; |
|||
p_hi_opt_dst->oversize_factor = p_hi_opt->oversize_factor; |
|||
p_hi_opt_dst->n_insert_first_oversize_check = |
|||
p_hi_opt->n_insert_first_oversize_check; |
|||
p_hi_opt_dst->n_insert_per_oversize_check = |
|||
p_hi_opt->n_insert_per_oversize_check; |
|||
} |
|||
|
|||
return rc_adj; |
|||
} /* end of function history_setopt */ |
|||
|
|||
|
|||
|
|||
/* This function gets current history options. |
|||
* |
|||
* Return codes |
|||
* 0: Options returned OK |
|||
* -1: Unknown structure size. Options not returned. |
|||
*/ |
|||
int history_getopt(const struct History_info *p_hi, |
|||
struct History_info_opt *p_hi_opt) |
|||
{ |
|||
/* Test for valid structure size */ |
|||
if (sizeof(struct History_info_opt) != p_hi_opt->n_byte_struct) { |
|||
return -1; |
|||
} |
|||
|
|||
*p_hi_opt = p_hi->hi_opt; |
|||
return 0; |
|||
} /* end of function hstory_getopt */ |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,55 @@ |
|||
/* Header for saving and retrieving history strings */ |
|||
#ifndef HIST_INFO_H |
|||
#define HIST_INFO_H |
|||
|
|||
struct History_info; |
|||
|
|||
/* Options for history logging */ |
|||
struct History_info_opt { |
|||
size_t n_byte_struct; /* sizeof(struct History_info_opt) |
|||
* Used for possible versioning */ |
|||
unsigned int n_str_init; /* initial number of allocated string locators |
|||
* Value must be at least 2 */ |
|||
unsigned int n_str_max; /* max number of string locators |
|||
* Value must be at least n_str_init when |
|||
* initializing but not when changing options. |
|||
* Then it must be at least 2 */ |
|||
size_t n_byte_str_buf_init; /* initial size of the string buffer. |
|||
* Value must be at least 2 */ |
|||
|
|||
/* If the amount of the buffer for storing strings that is in use |
|||
* multiplied by oversize_factor is less than the allocated amount, |
|||
* the buffer for storing strings is reduced by a factor of 2. |
|||
* Value must be at least 4. |
|||
* |
|||
* The first check for an oversized buffer is made after |
|||
* n_insert_first_oversize_check calls to history_add(). |
|||
* A value of 0 suppresses checks. |
|||
* |
|||
* Subsequent checks are made after n_insert_per_oversize_check calls |
|||
* to history_add(). |
|||
* A value of 0 suppresses checks after the first one. |
|||
*/ |
|||
unsigned int oversize_factor; |
|||
unsigned int n_insert_first_oversize_check; |
|||
unsigned int n_insert_per_oversize_check; |
|||
}; |
|||
|
|||
|
|||
struct History_info *history_init(struct History_info_opt *p_hi_opt); |
|||
int history_add(struct History_info **pp_hi, |
|||
unsigned int n_char_str, const char *str); |
|||
void history_free(struct History_info *p_hi);\ |
|||
const char *history_get_newest(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str); |
|||
const char *history_get_next(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str); |
|||
const char *history_get_prev(struct History_info *p_hi, |
|||
unsigned int *p_n_char_str); |
|||
void history_reset_pos(struct History_info *p_hi); |
|||
int history_getopt(const struct History_info *p_hi, |
|||
struct History_info_opt *p_hi_opt); |
|||
int history_setopt(struct History_info **pp_hi, |
|||
struct History_info_opt *p_hi_opt); |
|||
|
|||
#endif /* HIST_INFO_H include guard */ |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue