[git commit] shell: update HISTFILESIZE code to be actually useful
Denys Vlasenko
vda.linux at googlemail.com
Thu Jul 3 17:10:42 UTC 2025
commit: https://git.busybox.net/busybox/commit/?id=9c46a0688576dd8c67d2fc24b68c07402da14fc8
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master
"HISTFILESIZE=0" in profile wasn't working as intended,
"unset HISTFILE" wasn't preventing creation of history files
Now:
HISTSIZE=n allows to reduce in-memory history buffer
HISTFILESIZE=n allows to reduce history file size (0: truncate it)
unset HISTFILE allows to not save history file at all
function old new delta
exitshell 138 194 +56
hush_exit 97 143 +46
save_history 266 296 +30
hush_main 1170 1186 +16
.rodata 105762 105771 +9
load_history 246 254 +8
size_from_HISTFILESIZE 44 41 -3
read_line_input 2746 2712 -34
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 6/2 up/down: 165/-37) Total: 128 bytes
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
include/libbb.h | 2 +-
libbb/Config.src | 4 +--
libbb/lineedit.c | 98 +++++++++++++++++++++++++++++++-------------------------
shell/Config.src | 7 ++--
shell/ash.c | 26 +++++++++++++--
shell/hush.c | 41 ++++++++++++++++++++----
6 files changed, 120 insertions(+), 58 deletions(-)
diff --git a/include/libbb.h b/include/libbb.h
index 801f072fa..e765e18eb 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1989,7 +1989,7 @@ typedef struct line_input_t {
# if MAX_HISTORY
int cnt_history;
int cur_history;
- int max_history; /* must never be <= 0 */
+ int max_history; /* must never be < 0 */
# if ENABLE_FEATURE_EDITING_SAVEHISTORY
/* meaning of this field depends on FEATURE_EDITING_SAVE_ON_EXIT:
* if !FEATURE_EDITING_SAVE_ON_EXIT: "how many lines are
diff --git a/libbb/Config.src b/libbb/Config.src
index b980f19a9..55e670dcd 100644
--- a/libbb/Config.src
+++ b/libbb/Config.src
@@ -182,8 +182,8 @@ config FEATURE_EDITING_VI
config FEATURE_EDITING_HISTORY
int "History size"
# Don't allow way too big values here, code uses fixed "char *history[N]" struct member
- range 0 9999
- default 255
+ range 0 2000
+ default 200
depends on FEATURE_EDITING
help
Specify command history size (0 - disable).
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index 10a83bcb7..fe0dbc5b8 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -1406,8 +1406,8 @@ unsigned FAST_FUNC size_from_HISTFILESIZE(const char *hp)
int size = MAX_HISTORY;
if (hp) {
size = atoi(hp);
- if (size <= 0)
- return 1;
+ if (size < 0)
+ return 0;
if (size > MAX_HISTORY)
return MAX_HISTORY;
}
@@ -1501,18 +1501,21 @@ static void load_history(line_input_t *st_parm)
/* NB: do not trash old history if file can't be opened */
fp = fopen_for_read(st_parm->hist_file);
- if (fp) {
- /* clean up old history */
- for (idx = st_parm->cnt_history; idx > 0;) {
- idx--;
- free(st_parm->history[idx]);
- st_parm->history[idx] = NULL;
- }
+ if (!fp)
+ return;
- /* fill temp_h[], retaining only last MAX_HISTORY lines */
- memset(temp_h, 0, sizeof(temp_h));
- idx = 0;
- st_parm->cnt_history_in_file = 0;
+ /* clean up old history */
+ for (idx = st_parm->cnt_history; idx > 0;) {
+ idx--;
+ free(st_parm->history[idx]);
+ st_parm->history[idx] = NULL;
+ }
+
+ /* fill temp_h[], retaining only last max_history lines */
+ memset(temp_h, 0, sizeof(temp_h));
+ idx = 0;
+ st_parm->cnt_history_in_file = 0;
+ if (st_parm->max_history != 0) {
while ((line = xmalloc_fgetline(fp)) != NULL) {
if (line[0] == '\0') {
free(line);
@@ -1525,34 +1528,34 @@ static void load_history(line_input_t *st_parm)
if (idx == st_parm->max_history)
idx = 0;
}
- fclose(fp);
-
- /* find first non-NULL temp_h[], if any */
- if (st_parm->cnt_history_in_file) {
- while (temp_h[idx] == NULL) {
- idx++;
- if (idx == st_parm->max_history)
- idx = 0;
- }
- }
+ }
+ fclose(fp);
- /* copy temp_h[] to st_parm->history[] */
- for (i = 0; i < st_parm->max_history;) {
- line = temp_h[idx];
- if (!line)
- break;
+ /* find first non-NULL temp_h[], if any */
+ if (st_parm->cnt_history_in_file != 0) {
+ while (temp_h[idx] == NULL) {
idx++;
if (idx == st_parm->max_history)
idx = 0;
- line_len = strlen(line);
- if (line_len >= MAX_LINELEN)
- line[MAX_LINELEN-1] = '\0';
- st_parm->history[i++] = line;
}
- st_parm->cnt_history = i;
- if (ENABLE_FEATURE_EDITING_SAVE_ON_EXIT)
- st_parm->cnt_history_in_file = i;
}
+
+ /* copy temp_h[] to st_parm->history[] */
+ for (i = 0; i < st_parm->max_history;) {
+ line = temp_h[idx];
+ if (!line)
+ break;
+ idx++;
+ if (idx == st_parm->max_history)
+ idx = 0;
+ line_len = strlen(line);
+ if (line_len >= MAX_LINELEN)
+ line[MAX_LINELEN-1] = '\0';
+ st_parm->history[i++] = line;
+ }
+ st_parm->cnt_history = i;
+ if (ENABLE_FEATURE_EDITING_SAVE_ON_EXIT)
+ st_parm->cnt_history_in_file = i;
}
# if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
@@ -1563,14 +1566,23 @@ void FAST_FUNC save_history(line_input_t *st)
if (!st || !st->hist_file)
return;
if (st->cnt_history <= st->cnt_history_in_file)
- return;
+ return; /* no new entries were added */
+ /* note: if st->max_history is 0, we do not abort: we truncate the history to 0 lines */
- fp = fopen(st->hist_file, "a");
+ fp = fopen(st->hist_file, (st->max_history == 0 ? "w" : "a"));
if (fp) {
int i, fd;
char *new_name;
line_input_t *st_temp;
+ /* max_history==0 needs special-casing in general code,
+ * just handle it in a simpler way: */
+ if (st->max_history == 0) {
+ /* fopen("w") already truncated it */
+ fclose(fp);
+ return;
+ }
+
for (i = st->cnt_history_in_file; i < st->cnt_history; i++)
fprintf(fp, "%s\n", st->history[i]);
fclose(fp);
@@ -1580,6 +1592,8 @@ void FAST_FUNC save_history(line_input_t *st)
st_temp = new_line_input_t(st->flags);
st_temp->hist_file = st->hist_file;
st_temp->max_history = st->max_history;
+ /* load no more than max_history last lines */
+ /* (in unlikely case that file disappeared, st_temp gets empty history) */
load_history(st_temp);
/* write out temp file and replace hist_file atomically */
@@ -1609,7 +1623,6 @@ static void save_history(char *str)
fd = open(state->hist_file, O_WRONLY | O_CREAT | O_APPEND, 0600);
if (fd < 0)
return;
- xlseek(fd, 0, SEEK_END); /* paranoia */
len = strlen(str);
str[len] = '\n'; /* we (try to) do atomic write */
len2 = full_write(fd, str, len + 1);
@@ -1664,13 +1677,10 @@ static void remember_in_history(char *str)
if (str[0] == '\0')
return;
i = state->cnt_history;
- /* Don't save dupes */
- if (i && strcmp(state->history[i-1], str) == 0)
+ /* Don't save dups */
+ if (i != 0 && strcmp(state->history[i-1], str) == 0)
return;
- free(state->history[state->max_history]); /* redundant, paranoia */
- state->history[state->max_history] = NULL; /* redundant, paranoia */
-
/* If history[] is full, remove the oldest command */
/* we need to keep history[state->max_history] empty, hence >=, not > */
if (i >= state->max_history) {
@@ -1683,7 +1693,7 @@ static void remember_in_history(char *str)
state->cnt_history_in_file--;
# endif
}
- /* i <= state->max_history-1 */
+ /* i < state->max_history */
state->history[i++] = xstrdup(str);
/* i <= state->max_history */
state->cur_history = i;
diff --git a/shell/Config.src b/shell/Config.src
index 5efbf9995..5b3fe08f3 100644
--- a/shell/Config.src
+++ b/shell/Config.src
@@ -166,9 +166,10 @@ config FEATURE_SH_HISTFILESIZE
default y
depends on SHELL_ASH || SHELL_HUSH
help
- This option makes busybox shells to use $HISTFILESIZE variable
- to set shell history size. Note that its max value is capped
- by "History size" setting in library tuning section.
+ This option makes busybox shells to use $HISTSIZE and
+ $HISTFILESIZE variables to set shell history size.
+ Note that its max value is capped by "History size" setting
+ in library tuning section.
config FEATURE_SH_EMBEDDED_SCRIPTS
bool "Embed scripts in the binary"
diff --git a/shell/ash.c b/shell/ash.c
index 16eb88a7b..18344767a 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -14525,8 +14525,25 @@ exitshell(void)
char *p;
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
- save_history(line_input_state); /* may be NULL */
+ if (line_input_state) {
+ const char *hp;
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+// in bash:
+// HISTFILESIZE controls the on-disk history file size (in lines, 0=no history):
+// "When this variable is assigned a value, the history file is truncated, if necessary"
+// but we do it only at exit, not on assignment:
+ /* Use HISTFILESIZE to limit file size */
+ hp = lookupvar("HISTFILESIZE");
+ if (hp)
+ line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
+ /* HISTFILE: "If unset, the command history is not saved when a shell exits." */
+ hp = lookupvar("HISTFILE");
+ line_input_state->hist_file = hp;
+ save_history(line_input_state); /* no-op if hist_file is NULL */
+ }
#endif
+
savestatus = exitstatus;
TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus));
if (setjmp(loc.loc))
@@ -14867,7 +14884,12 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
if (hp)
line_input_state->hist_file = xstrdup(hp);
# if ENABLE_FEATURE_SH_HISTFILESIZE
- hp = lookupvar("HISTFILESIZE");
+ hp = lookupvar("HISTSIZE");
+ /* Using HISTFILESIZE above to limit max_history would be WRONG:
+ * users may set HISTFILESIZE=0 in their profile scripts
+ * to prevent _saving_ of history files, but still want to have
+ * non-zero history limit for in-memory list.
+ */
line_input_state->max_history = size_from_HISTFILESIZE(hp);
# endif
}
diff --git a/shell/hush.c b/shell/hush.c
index 70b730f67..68aca53a3 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -2099,11 +2099,29 @@ static sighandler_t pick_sighandler(unsigned sig)
return handler;
}
+static const char* FAST_FUNC get_local_var_value(const char *name);
+
/* Restores tty foreground process group, and exits. */
static void hush_exit(int exitcode)
{
#if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT
- save_history(G.line_input_state); /* may be NULL */
+ if (G.line_input_state) {
+ const char *hp;
+# if ENABLE_FEATURE_SH_HISTFILESIZE
+// in bash:
+// HISTFILESIZE controls the on-disk history file size (in lines, 0=no history):
+// "When this variable is assigned a value, the history file is truncated, if necessary"
+// but we do it only at exit, not on every assignment:
+ /* Use HISTFILESIZE to limit file size */
+ hp = get_local_var_value("HISTFILESIZE");
+ if (hp)
+ G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+# endif
+ /* HISTFILE: "If unset, the command history is not saved when a shell exits." */
+ hp = get_local_var_value("HISTFILE");
+ G.line_input_state->hist_file = hp;
+ save_history(G.line_input_state); /* no-op if hist_file is NULL */
+ }
#endif
fflush_all();
@@ -10427,7 +10445,7 @@ int hush_main(int argc, char **argv)
if (!get_local_var_value("PATH"))
set_local_var_from_halves("PATH", bb_default_root_path);
- /* PS1/PS2 are set later, if we determine that we are interactive */
+ /* PS1/PS2/HISTFILE are set later, if we determine that we are interactive */
/* bash also exports SHLVL and _,
* and sets (but doesn't export) the following variables:
@@ -10449,7 +10467,6 @@ int hush_main(int argc, char **argv)
* BASH_SOURCE=()
* DIRSTACK=()
* PIPESTATUS=([0]="0")
- * HISTFILE=/<xxx>/.bash_history
* HISTFILESIZE=500
* HISTSIZE=500
* MAILCHECK=60
@@ -10809,18 +10826,30 @@ int hush_main(int argc, char **argv)
const char *hp = get_local_var_value("HISTFILE");
if (!hp) {
hp = get_local_var_value("HOME");
- if (hp)
+ if (hp) {
hp = concat_path_file(hp, ".hush_history");
+ /* Make HISTFILE set on exit (else history won't be saved) */
+ set_local_var_from_halves("HISTFILE", hp);
+ }
} else {
hp = xstrdup(hp);
}
if (hp) {
G.line_input_state->hist_file = hp;
- //set_local_var(xasprintf("HISTFILE=%s", ...));
}
# if ENABLE_FEATURE_SH_HISTFILESIZE
- hp = get_local_var_value("HISTFILESIZE");
+ hp = get_local_var_value("HISTSIZE");
+ /* Using HISTFILESIZE above to limit max_history would be WRONG:
+ * users may set HISTFILESIZE=0 in their profile scripts
+ * to prevent _saving_ of history files, but still want to have
+ * non-zero history limit for in-memory list.
+ */
+// in bash, runtime history size is controlled by HISTSIZE (0=no history),
+// HISTFILESIZE controls on-disk history file size (in lines, 0=no history):
G.line_input_state->max_history = size_from_HISTFILESIZE(hp);
+// HISTFILESIZE: "The shell sets the default value to the value of HISTSIZE after reading any startup files."
+// HISTSIZE: "The shell sets the default value to 500 after reading any startup files."
+// (meaning: if the value wasn't set after startup files, the default value is set as described above)
# endif
}
# endif
More information about the busybox-cvs
mailing list