/* bashhist.c -- bash interface to the GNU history library. */
-/* Copyright (C) 1993-2015 Free Software Foundation, Inc.
+/* Copyright (C) 1993-2021 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
#include "shell.h"
#include "flags.h"
+#include "parser.h"
#include "input.h"
#include "parser.h" /* for the struct dstack stuff. */
#include "pathexp.h" /* for the struct ignorevar stuff */
extern int rl_done, rl_dispatching; /* should really include readline.h */
#endif
+#ifndef HISTSIZE_DEFAULT
+# define HISTSIZE_DEFAULT "500"
+#endif
+
#if !defined (errno)
extern int errno;
#endif
-static int histignore_item_func __P((struct ign *));
-static int check_history_control __P((char *));
-static void hc_erasedups __P((char *));
-static void really_add_history __P((char *));
+static int histignore_item_func PARAMS((struct ign *));
+static int check_history_control PARAMS((char *));
+static void hc_erasedups PARAMS((char *));
+static void really_add_history PARAMS((char *));
static struct ignorevar histignore =
{
list. This is different than the user-controlled behaviour; this
becomes zero when we read lines from a file, for example. */
int remember_on_history = 0;
-int enable_history_list = 0; /* value for `set -o history' */
+int enable_history_list = -1; /* value for `set -o history' */
/* The number of lines that Bash has added to this history session. The
difference between the number of the top element in the history list
the history-manipluating builtins can see it. */
int current_command_first_line_saved = 0;
+/* Set to the number of the most recent line of a possibly-multi-line command
+ that contains a shell comment. Used by bash_add_history() to determine
+ whether to add a newline or a semicolon. */
+int current_command_line_comment = 0;
+
/* Non-zero means to store newlines in the history list when using
command_oriented_history rather than trying to use semicolons. */
int literal_history;
/* Non-zero means to not save function definitions in the history list. */
int dont_save_function_defs;
-/* Variables declared in other files used here. */
-extern int current_command_line_count;
-
-extern struct dstack dstack;
-extern int parser_state;
-
#if defined (BANG_HISTORY)
-static int bash_history_inhibit_expansion __P((char *, int));
+static int bash_history_inhibit_expansion PARAMS((char *, int));
#endif
#if defined (READLINE)
-static void re_edit __P((char *));
+static void re_edit PARAMS((char *));
#endif
-static int history_expansion_p __P((char *));
-static int shell_comment __P((char *));
-static int should_expand __P((char *));
-static HIST_ENTRY *last_history_entry __P((void));
-static char *expand_histignore_pattern __P((char *));
-static int history_should_ignore __P((char *));
+static int history_expansion_p PARAMS((char *));
+static int shell_comment PARAMS((char *));
+static int should_expand PARAMS((char *));
+static HIST_ENTRY *last_history_entry PARAMS((void));
+static char *expand_histignore_pattern PARAMS((char *));
+static int history_should_ignore PARAMS((char *));
#if defined (BANG_HISTORY)
/* Is the history expansion starting at string[i] one that should not
char *string;
int i;
{
- int t;
+ int t, si;
char hx[2];
hx[0] = history_expansion_char;
return (1);
#endif
+ si = 0;
+ /* If we're supposed to be in single-quoted string, skip over the
+ single-quoted part and then look at what's left. */
+ if (history_quoting_state == '\'')
+ {
+ si = skip_to_delim (string, 0, "'", SD_NOJMP|SD_HISTEXP);
+ if (string[si] == 0 || si >= i)
+ return (1);
+ si++;
+ }
+
/* Make sure the history expansion should not be skipped by quoting or
command/process substitution. */
- else if ((t = skip_to_histexp (string, 0, hx, SD_NOJMP|SD_HISTEXP)) > 0)
+ if ((t = skip_to_histexp (string, si, hx, SD_NOJMP|SD_HISTEXP)) > 0)
{
/* Skip instances of history expansion appearing on the line before
this one. */
int interact;
{
#if defined (BANG_HISTORY)
- history_expansion = interact != 0;
- history_expansion_inhibited = 1; /* XXX */
+ history_expansion = (interact == 0) ? histexp_flag : HISTEXPAND_DEFAULT;
+ history_expansion_inhibited = (interact == 0) ? 1 - histexp_flag : 0; /* changed in bash_history_enable() */
history_inhibit_expansion_function = bash_history_inhibit_expansion;
#endif
remember_on_history = enable_history_list;
Note that the history file is automatically truncated to the
size of HISTSIZE if the user does not explicitly set the size
differently. */
- set_if_not ("HISTSIZE", "500");
+ set_if_not ("HISTSIZE", HISTSIZE_DEFAULT);
sv_histsize ("HISTSIZE");
set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE"));
discard = remove_history (i);
if (discard)
- free_history_entry (discard);
- history_lines_this_session--;
+ {
+ free_history_entry (discard);
+ history_lines_this_session--;
+ }
+ return discard != 0;
+}
+
+int
+bash_delete_history_range (first, last)
+ int first, last;
+{
+ register int i;
+ HIST_ENTRY **discard_list;
+
+ discard_list = remove_history_range (first, last);
+ if (discard_list == 0)
+ return 0;
+ for (i = 0; discard_list[i]; i++)
+ free_history_entry (discard_list[i]);
+ free (discard_list);
+ history_lines_this_session -= i;
return 1;
}
maybe_append_history (filename)
char *filename;
{
- int fd, result;
+ int fd, result, histlen;
struct stat buf;
result = EXECUTION_SUCCESS;
- if (history_lines_this_session > 0 && (history_lines_this_session <= where_history ()))
+ if (history_lines_this_session > 0)
{
/* If the filename was supplied, then create it if necessary. */
if (stat (filename, &buf) == -1 && errno == ENOENT)
}
close (fd);
}
+ /* cap the number of lines we write at the length of the history list */
+ histlen = where_history ();
+ if (histlen > 0 && history_lines_this_session > histlen)
+ history_lines_this_session = histlen; /* reset below anyway */
result = append_history (history_lines_this_session, filename);
/* Pretend we already read these lines from the file because we just
added them */
add that line to the history if ADDIT is non-zero. */
if (!history_expansion_inhibited && history_expansion && history_expansion_p (line))
{
+ int old_len;
+
+ /* If we are expanding the second or later line of a multi-line
+ command, decrease history_length so references to history expansions
+ in these lines refer to the previous history entry and not the
+ current command. */
+ old_len = history_length;
+ if (history_length > 0 && command_oriented_history && current_command_first_line_saved && current_command_line_count > 1)
+ history_length--;
expanded = history_expand (line, &history_value);
+ if (history_length >= 0 && command_oriented_history && current_command_first_line_saved && current_command_line_count > 1)
+ history_length = old_len;
if (expanded)
{
}
/* Return 1 if the first non-whitespace character in LINE is a `#', indicating
- * that the line is a shell comment. */
+ that the line is a shell comment. Return 2 if there is a comment after the
+ first non-whitespace character. Return 0 if the line does not contain a
+ comment. */
static int
shell_comment (line)
char *line;
{
char *p;
+ int n;
+ if (dstack.delimiter_depth != 0 || (parser_state & PST_HEREDOC))
+ return 0;
+ if (line == 0)
+ return 0;
for (p = line; p && *p && whitespace (*p); p++)
;
- return (p && *p == '#');
+ if (p && *p == '#')
+ return 1;
+ n = skip_to_delim (line, p - line, "#", SD_NOJMP|SD_GLOB|SD_EXTGLOB|SD_COMPLETE);
+ return (line[n] == '#') ? 2 : 0;
}
#ifdef INCLUDE_UNUSED
maybe_add_history (line)
char *line;
{
+ int is_comment;
+
hist_last_line_added = 0;
+ is_comment = shell_comment (line);
/* Don't use the value of history_control to affect the second
and subsequent lines of a multi-line command (old code did
if (current_command_line_count > 1)
{
if (current_command_first_line_saved &&
- ((parser_state & PST_HEREDOC) || literal_history || dstack.delimiter_depth != 0 || shell_comment (line) == 0))
+ ((parser_state & PST_HEREDOC) || literal_history || dstack.delimiter_depth != 0 || is_comment != 1))
bash_add_history (line);
+ current_command_line_comment = is_comment ? current_command_line_count : -2;
return;
}
/* This is the first line of a (possible multi-line) command. Note whether
or not we should save the first line and remember it. */
+ current_command_line_comment = is_comment ? current_command_line_count : -2;
current_command_first_line_saved = check_add_history (line, 0);
}
}
#if defined (SYSLOG_HISTORY)
-#define SYSLOG_MAXLEN 600
-
-extern char *shell_name;
+#define SYSLOG_MAXMSG 1024
+#define SYSLOG_MAXLEN SYSLOG_MAXMSG
+#define SYSLOG_MAXHDR 256
#ifndef OPENLOG_OPTS
#define OPENLOG_OPTS 0
#endif
+#if defined (SYSLOG_SHOPT)
+int syslog_history = SYSLOG_SHOPT;
+#else
+int syslog_history = 1;
+#endif
+
void
bash_syslog_history (line)
const char *line;
{
- char trunc[SYSLOG_MAXLEN];
+ char trunc[SYSLOG_MAXLEN], *msg;
+ char loghdr[SYSLOG_MAXHDR];
+ char seqbuf[32], *seqnum;
+ int hdrlen, msglen, seqlen, chunks, i;
static int first = 1;
if (first)
first = 0;
}
- if (strlen(line) < SYSLOG_MAXLEN)
- syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, line);
+ hdrlen = snprintf (loghdr, sizeof(loghdr), "HISTORY: PID=%d UID=%d", getpid(), current_user.uid);
+ msglen = strlen (line);
+
+ if ((msglen + hdrlen + 1) < SYSLOG_MAXLEN)
+ syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "%s %s", loghdr, line);
else
{
- strncpy (trunc, line, SYSLOG_MAXLEN);
- trunc[SYSLOG_MAXLEN - 1] = '\0';
- syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY (TRUNCATED): PID=%d UID=%d %s", getpid(), current_user.uid, trunc);
+ chunks = ((msglen + hdrlen) / SYSLOG_MAXLEN) + 1;
+ for (msg = line, i = 0; i < chunks; i++)
+ {
+ seqnum = inttostr (i + 1, seqbuf, sizeof (seqbuf));
+ seqlen = STRLEN (seqnum);
+
+ /* 7 == "(seq=) " */
+ strncpy (trunc, msg, SYSLOG_MAXLEN - hdrlen - seqlen - 7 - 1);
+ trunc[SYSLOG_MAXLEN - 1] = '\0';
+ syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "%s (seq=%s) %s", loghdr, seqnum, trunc);
+ msg += SYSLOG_MAXLEN - hdrlen - seqlen - 8;
+ }
}
}
#endif
bash_add_history (line)
char *line;
{
- int add_it, offset, curlen;
+ int add_it, offset, curlen, is_comment;
HIST_ENTRY *current, *old;
char *chars_to_add, *new_line;
add_it = 1;
if (command_oriented_history && current_command_line_count > 1)
{
+ is_comment = shell_comment (line);
+
/* The second and subsequent lines of a here document have the trailing
newline preserved. We don't want to add extra newlines here, but we
do want to add one after the first line (which is the command that
does the right thing to take care of this for us. We don't want to
add extra newlines if the user chooses to enable literal_history,
so we have to duplicate some of what that function does here. */
- if ((parser_state & PST_HEREDOC) && literal_history && current_command_line_count > 2 && line[strlen (line) - 1] == '\n')
+ /* If we're in a here document and past the first line,
+ (current_command_line_count > 2)
+ don't add a newline here. This will also take care of the literal_history
+ case if the other conditions are met. */
+ if ((parser_state & PST_HEREDOC) && here_doc_first_line == 0 && line[strlen (line) - 1] == '\n')
chars_to_add = "";
+ else if (current_command_line_count == current_command_line_comment+1)
+ chars_to_add = "\n";
+ else if (literal_history)
+ chars_to_add = "\n";
else
- chars_to_add = literal_history ? "\n" : history_delimiting_chars (line);
+ chars_to_add = history_delimiting_chars (line);
using_history ();
current = previous_history ();
+ current_command_line_comment = is_comment ? current_command_line_count : -2;
+
if (current)
{
/* If the previous line ended with an escaped newline (escaped
}
}
+ if (add_it && history_is_stifled() && history_length == 0 && history_length == history_max_entries)
+ add_it = 0;
+
if (add_it)
really_add_history (line);
#if defined (SYSLOG_HISTORY)
- bash_syslog_history (line);
+ if (syslog_history)
+ bash_syslog_history (line);
#endif
using_history ();
history_number ()
{
using_history ();
- return (remember_on_history ? history_base + where_history () : 1);
+ return ((remember_on_history || enable_history_list) ? history_base + where_history () : 1);
}
static int