*
* 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"
struct Str_info {
unsigned int n_byte_sz;
char *sz;
};
struct History_info {
struct History_info_opt hi_opt;
bool f_first_resize_check_done;
unsigned int n_str_cur;
unsigned int n_str_alloc;
unsigned int n_insert_since_resize_check;
* string insertions, the buffer will be reduced
* in size if it is excessively large for the data */
size_t n_byte_buf_cur;
size_t n_byte_buf_alloc;
unsigned int index_str_start;
unsigned int index_str_cur;
unsigned int index_str_to_return;
char *p_char_buf;
char *p_char_buf_start;
char *p_char_buf_cur;
char *p_char_buf_end;
* 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];
};
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);
*
* 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;
if (adjust_history_options(p_hi_opt, true) < 0) {
return (struct History_info *) NULL;
}
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;
}
p_hi->hi_opt = *p_hi_opt;
p_hi->f_first_resize_check_done = false;
return p_hi;
}
*
* 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)
{
if (p_hi_opt->n_byte_struct != sizeof(struct History_info_opt)) {
return -1;
}
int xrc = 0;
if (p_hi_opt->n_str_init < 2) {
if (f_is_init) {
xrc = +1;
}
p_hi_opt->n_str_init = 2;
}
* 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;
}
}
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;
}
if (p_hi_opt->oversize_factor < 4) {
xrc = +1;
p_hi_opt->oversize_factor = 4;
}
return xrc;
}
*
* 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;
* 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);
const size_t n_byte_alloc = offset_char_buf + n_byte_char_buf;
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;
{
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;
}
history_make_empty(p_hi);
return p_hi;
}
* 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;
p_hi_dst->hi_opt = p_hi_src->hi_opt;
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 (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;
unsigned int index_cur_src;
unsigned int index_str_start_src;
* to be copied */
* variable */
if (n_str_src > p_hi_dst->n_str_alloc) {
* 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;
}
else {
* beginning */
index_str_start_src = p_hi_src->index_str_start;
}
if ((index_cur_src = p_hi_src->index_str_cur - 1) == (unsigned int) -1) {
index_cur_src += p_hi_src->n_str_alloc;
}
unsigned int index_cur_dst = p_hi_dst->n_str_alloc - 1;
* 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;
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;
* newest element so that if truncation occurs, it will be the oldest
* strings that get truncated */
for ( ; ; ) {
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;
if ((p_dst_cur -= n_byte_str_cur) < p_char_buf_dst) {
break;
}
(void) memcpy(p_dst_cur, p_str_info_src->sz, n_byte_str_cur);
n_byte_buf_cur_dst += n_byte_str_cur;
p_str_info_dst->sz = p_dst_cur;
p_str_info_dst->n_byte_sz = n_byte_str_cur;
* 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;
}
if (--index_cur_src == (unsigned int) -1) {
index_cur_src += n_str_src_alloc;
}
--index_cur_dst;
}
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;
p_hi_dst->index_str_cur = 0;
p_hi_dst->p_char_buf_start = p_dst_cur;
p_hi_dst->p_char_buf_cur = p_hi_dst->p_char_buf;
* If the earlier command no longer exists, set to the closest value */
{
* 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;
* 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;
* 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;
* 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;
if (p_hi_dst->index_str_cur >= offset_from_cur_dst) {
p_hi_dst->index_str_to_return =
p_hi_dst->index_str_cur - offset_from_cur_dst;
}
else {
p_hi_dst->index_str_to_return =
p_hi_dst->index_str_cur +
(p_hi_dst->n_str_alloc - offset_from_cur_dst);
}
}
}
return xrc;
}
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;
}
* 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);
}
* 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;
if (n_str == 0) {
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) {
if (index_str_to_return == 0) {
index_str_to_return = n_str - 1;
}
else {
--index_str_to_return;
}
}
else {
if (index_str_to_return == 0) {
if (p_hi->index_str_start < p_hi->index_str_cur) {
index_str_to_return = p_hi->index_str_cur - 1;
}
else {
* is a second piece of data in [0, end-1] */
index_str_to_return = p_hi->n_str_alloc - 1;
}
}
else {
if (index_str_to_return == p_hi->index_str_start) {
if (p_hi->index_str_cur == 0) {
index_str_to_return = p_hi->n_str_alloc - 1;
}
else {
index_str_to_return = p_hi->index_str_cur - 1;
}
}
else {
--index_str_to_return;
}
}
}
if (f_update_pos) {
p_hi->index_str_to_return = index_str_to_return;
}
return return_str_data(p_hi, index_str_to_return, p_n_char_str);
}
* 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);
}
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;
if (n_str == 0) {
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) {
index_str_to_return = p_hi->index_str_start;
}
else {
if (n_str == p_hi->n_str_alloc) {
if (index_str_to_return == n_str - 1) {
index_str_to_return = 0;
}
else {
++index_str_to_return;
}
}
else {
if (index_str_to_return == p_hi->n_str_alloc - 1) {
if (p_hi->index_str_start < p_hi->index_str_cur) {
index_str_to_return = p_hi->index_str_start;
}
else {
* 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;
}
}
}
else {
if (index_str_to_return == p_hi->index_str_cur - 1) {
index_str_to_return = p_hi->index_str_start;
}
else {
++index_str_to_return;
}
}
}
}
p_hi->index_str_to_return = index_str_to_return;
return return_str_data(p_hi, index_str_to_return, p_n_char_str);
}
* 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;
if (p_n_char_str != (unsigned *) NULL) {
*p_n_char_str = p_str_info_cur->n_byte_sz - 1;
}
return p_str_info_cur->sz;
}
* 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;
if ((p_hi_new = history_alloc(n_str_alloc, n_byte_char_buf_alloc)) ==
(struct History_info *) NULL) {
return -1;
}
const int xrc = history_copy(p_hi_new, p_hi_old);
history_free(p_hi_old);
*pp_hi = p_hi_new;
return xrc;
}
void history_free(struct History_info *p_hi)
{
if (p_hi != (struct History_info *) NULL) {
free((void *) p_hi);
}
return;
}
* 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;
struct History_info *p_hi = *pp_hi;
char *p_dst = (char *) NULL;
bool f_have_room;
* 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 (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;
f_have_room = true;
}
else {
f_have_room = false;
}
}
else {
f_have_room = true;
}
* 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;
}
p_hi->p_char_buf_start =
p_hi->p_str_info[p_hi->index_str_start].sz;
--p_hi->n_str_cur;
}
* buffer will be enlarged via a new history information structure. */
{
* (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) {
* 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 {
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) {
* 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;
}
}
}
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;
}
*pp_hi = p_hi;
* without any checks */
p_dst = p_hi->p_char_buf_cur;
}
{
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;
}
if (++p_hi->index_str_cur == p_hi->n_str_alloc) {
p_hi->index_str_cur = 0;
}
p_hi->index_str_to_return = UINT_MAX;
p_hi->n_byte_buf_cur += n_byte_data;
(void) memcpy(p_dst, str, n_char_str);
p_dst += n_char_str;
*p_dst++ = '\0';
* writing the string */
p_hi->p_char_buf_cur = p_dst;
++p_hi->n_str_cur;
* 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;
if (p_hi->f_first_resize_check_done) {
const unsigned int n =
p_hi->hi_opt.n_insert_per_oversize_check;
if (n != 0 && n_insert_since_check >= n) {
f_do_check = true;
}
}
else {
const unsigned int n =
p_hi->hi_opt.n_insert_first_oversize_check;
if (n != 0 && n_insert_since_check >= n) {
f_do_check = true;
p_hi->f_first_resize_check_done = true;
}
}
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) {
* 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;
}
}
}
return 0;
}
* 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;
}
* 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;
if ((rc_adj = adjust_history_options(p_hi_opt, 0)) < 0) {
return +2;
}
struct History_info *p_hi = *pp_hi;
* 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;
}
p_hi->hi_opt.n_str_max = n_str_max_new;
*pp_hi = p_hi;
}
}
{
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;
}
*
* 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)
{
if (sizeof(struct History_info_opt) != p_hi_opt->n_byte_struct) {
return -1;
}
*p_hi_opt = p_hi->hi_opt;
return 0;
}