summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--0001-sqlite-history.patch1064
1 files changed, 1064 insertions, 0 deletions
diff --git a/0001-sqlite-history.patch b/0001-sqlite-history.patch
new file mode 100644
index 0000000..d4be939
--- /dev/null
+++ b/0001-sqlite-history.patch
@@ -0,0 +1,1064 @@
+diff --git a/bashhist.c b/bashhist.c
+index 9979f99a..d411eb12 100644
+--- a/bashhist.c
++++ b/bashhist.c
+@@ -22,6 +22,10 @@
+
+ #if defined (HISTORY)
+
++#if defined (HAVE_SQLITE3) && defined (HAVE_SQLITE3_H)
++# include "bashhist_sqlite.c"
++#else
++
+ #if defined (HAVE_UNISTD_H)
+ # ifdef _MINIX
+ # include <sys/types.h>
+@@ -975,4 +979,5 @@ history_should_ignore (line)
+
+ return match;
+ }
++#endif /* !HAVE_SQLITE3 */
+ #endif /* HISTORY */
+diff --git a/config.h.in b/config.h.in
+index a5ad9e72..656ad16d 100644
+--- a/config.h.in
++++ b/config.h.in
+@@ -703,6 +703,9 @@
+ /* Define if you have the killpg function. */
+ #undef HAVE_KILLPG
+
++/* Define if sqlite3 library is available. */
++#undef HAVE_SQLITE3
++
+ /* Define if you have the lstat function. */
+ #undef HAVE_LSTAT
+
+@@ -985,6 +988,9 @@
+ /* Define if you have the <regex.h> header file. */
+ #undef HAVE_REGEX_H
+
++/* Define if you have the <sqlite3.h> header file. */
++#undef HAVE_SQLITE3_H
++
+ /* Define if you have the <stdlib.h> header file. */
+ #undef HAVE_STDLIB_H
+
+diff --git a/configure.ac b/configure.ac
+index ce4e9b60..7140edf9 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -862,6 +862,13 @@ AC_CHECK_LIB(dl, dlopen)
+ AC_CHECK_FUNCS(dlopen dlclose dlsym)
+ fi
+
++dnl check for sqlite3 (used for history storage)
++AC_CHECK_LIB([sqlite3], [sqlite3_open_v2], [
++ AC_DEFINE([HAVE_SQLITE3], [1], [Define if sqlite3 library is available])
++ LIBS="-lsqlite3 $LIBS"
++])
++AC_CHECK_HEADERS([sqlite3.h])
++
+ dnl this defines HAVE_DECL_SYS_SIGLIST
+ AC_DECL_SYS_SIGLIST
+
+--- /dev/null 2026-04-01 12:42:23.952423856 -0400
++++ bashhist_sqlite.c 2026-04-01 22:30:04.382338607 -0400
+@@ -0,0 +1,998 @@
++/* bashhist_sqlite.c -- SQLite-backed history for bash.
++ *
++ * Drop-in replacement for the body of bashhist.c. Included (not compiled
++ * separately) when HAVE_SQLITE3 && HAVE_SQLITE3_H are both defined.
++ *
++ * Differences from the stock file:
++ * - History is stored in $HISTFILE.db (SQLite, WAL mode) instead of the
++ * flat $HISTFILE text file.
++ * - Every command is written to the DB immediately on add, so history
++ * survives shell crashes without needing HISTAPPEND / HISTFILESIZE tricks.
++ * - HISTSIZE still limits the readline in-memory list; the DB keeps everything.
++ * - Timestamps, CWD, hostname, and PID are stored per-entry.
++ * - erasedups (HISTCONTROL=erasedups) removes duplicates from the DB too,
++ * across all sessions.
++ * - history -c clears the DB.
++ * - history -a / exit are no-ops for file I/O (already persisted).
++ * - history -r / -w / -n still operate on the plain text file for
++ * compatibility / export use.
++ */
++
++#include <sqlite3.h>
++#include <time.h>
++
++#if defined (HAVE_UNISTD_H)
++# ifdef _MINIX
++# include <sys/types.h>
++# endif
++# include <unistd.h>
++#endif
++
++#include "bashtypes.h"
++#include <stdio.h>
++#include <errno.h>
++#include "bashansi.h"
++#include "posixstat.h"
++#include "filecntl.h"
++
++#include "bashintl.h"
++
++#if defined (SYSLOG_HISTORY)
++# include <syslog.h>
++#endif
++
++#include "shell.h"
++#include "flags.h"
++#include "input.h"
++#include "parser.h"
++#include "pathexp.h"
++#include "bashhist.h"
++#include "builtins/common.h"
++
++#include <readline/history.h>
++#include <glob/glob.h>
++#include <glob/strmatch.h>
++
++#if defined (READLINE)
++# include "bashline.h"
++extern int rl_done, rl_dispatching;
++#endif
++
++#if !defined (errno)
++extern int errno;
++#endif
++
++/* ------------------------------------------------------------------ */
++/* SQLite state */
++/* ------------------------------------------------------------------ */
++
++static sqlite3 *hist_db = NULL;
++static int sqlite_pending_erasedups = 0;
++
++/* Forward declaration — sqlite_history_add calls this. */
++static void sqlite_history_erasedups __P((const char *));
++
++static char *
++sqlite_history_dbpath (const char *histfile)
++{
++ char *p;
++ size_t len;
++
++ if (histfile == NULL || *histfile == '\0')
++ {
++ const char *home = get_string_value ("HOME");
++ if (home == NULL) return NULL;
++ len = strlen (home) + 20;
++ p = xmalloc (len);
++ snprintf (p, len, "%s/.bash_history.db", home);
++ return p;
++ }
++
++ len = strlen (histfile) + 4;
++ p = xmalloc (len);
++ snprintf (p, len, "%s.db", histfile);
++ return p;
++}
++
++static int
++sqlite_history_open (const char *histfile)
++{
++ char *dbpath;
++ int rc;
++
++ if (hist_db != NULL)
++ return 0;
++
++ dbpath = sqlite_history_dbpath (histfile);
++ if (dbpath == NULL)
++ return -1;
++
++ rc = sqlite3_open (dbpath, &hist_db);
++ xfree (dbpath);
++
++ if (rc != SQLITE_OK)
++ {
++ hist_db = NULL;
++ return -1;
++ }
++
++ rc = sqlite3_exec (hist_db,
++ "PRAGMA journal_mode=WAL;"
++ "PRAGMA synchronous=NORMAL;"
++ "CREATE TABLE IF NOT EXISTS history ("
++ " id INTEGER PRIMARY KEY AUTOINCREMENT,"
++ " command TEXT NOT NULL,"
++ " timestamp INTEGER NOT NULL DEFAULT (strftime('%s','now')),"
++ " session INTEGER,"
++ " hostname TEXT,"
++ " cwd TEXT"
++ ");"
++ "CREATE INDEX IF NOT EXISTS idx_hist_cmd ON history(command);",
++ NULL, NULL, NULL);
++
++ if (rc != SQLITE_OK)
++ {
++ sqlite3_close (hist_db);
++ hist_db = NULL;
++ return -1;
++ }
++
++ return 0;
++}
++
++/* Load the most-recent MAX_ENTRIES rows into readline's in-memory list.
++ Pass max_entries <= 0 to load everything. */
++static int
++sqlite_history_load (int max_entries)
++{
++ sqlite3_stmt *stmt;
++ int rc, count;
++ char sql[512];
++
++ if (hist_db == NULL)
++ return -1;
++
++ if (max_entries > 0)
++ snprintf (sql, sizeof (sql),
++ "SELECT id, command, timestamp FROM ("
++ " SELECT id, command, timestamp FROM history ORDER BY id DESC LIMIT %d"
++ ") ORDER BY id ASC",
++ max_entries);
++ else
++ snprintf (sql, sizeof (sql),
++ "SELECT id, command, timestamp FROM history ORDER BY id ASC");
++
++ rc = sqlite3_prepare_v2 (hist_db, sql, -1, &stmt, NULL);
++ if (rc != SQLITE_OK)
++ return -1;
++
++ count = 0;
++ while (sqlite3_step (stmt) == SQLITE_ROW)
++ {
++ const char *cmd = (const char *) sqlite3_column_text (stmt, 1);
++ sqlite3_int64 ts = sqlite3_column_int64 (stmt, 2);
++
++ if (cmd && *cmd)
++ {
++ add_history (cmd);
++ if (ts > 0)
++ {
++ char tsbuf[32];
++ snprintf (tsbuf, sizeof (tsbuf), "#%lld", (long long) ts);
++ add_history_time (tsbuf);
++ }
++ count++;
++ }
++ }
++
++ sqlite3_finalize (stmt);
++ history_lines_read_from_file = count;
++ return count;
++}
++
++static void
++sqlite_history_add (const char *line)
++{
++ sqlite3_stmt *stmt;
++ time_t now;
++ char cwd[4096];
++ const char *hostname;
++
++ if (hist_db == NULL)
++ return;
++
++ time (&now);
++ hostname = get_string_value ("HOSTNAME");
++ if (getcwd (cwd, sizeof (cwd)) == NULL)
++ cwd[0] = '\0';
++
++ if (sqlite3_prepare_v2 (hist_db,
++ "INSERT INTO history (command, timestamp, session, hostname, cwd)"
++ " VALUES (?, ?, ?, ?, ?)",
++ -1, &stmt, NULL) != SQLITE_OK)
++ return;
++
++ sqlite3_bind_text (stmt, 1, line, -1, SQLITE_STATIC);
++ sqlite3_bind_int64 (stmt, 2, (sqlite3_int64) now);
++ sqlite3_bind_int (stmt, 3, (int) getpid ());
++ sqlite3_bind_text (stmt, 4, hostname ? hostname : "", -1, SQLITE_STATIC);
++ sqlite3_bind_text (stmt, 5, cwd, -1, SQLITE_STATIC);
++
++ sqlite3_step (stmt);
++ sqlite3_finalize (stmt);
++
++ /* hc_erasedups() already removed in-memory duplicates and set the flag;
++ now remove DB duplicates after the insert so the new row has the MAX id
++ and the DELETE keeps it. */
++ if (sqlite_pending_erasedups)
++ {
++ sqlite_pending_erasedups = 0;
++ sqlite_history_erasedups (line);
++ }
++}
++
++/* Keep only the most recent occurrence of LINE in the DB. */
++static void
++sqlite_history_erasedups (const char *line)
++{
++ sqlite3_stmt *stmt;
++
++ if (hist_db == NULL)
++ return;
++
++ if (sqlite3_prepare_v2 (hist_db,
++ "DELETE FROM history WHERE command = ?"
++ " AND id != (SELECT MAX(id) FROM history WHERE command = ?)",
++ -1, &stmt, NULL) != SQLITE_OK)
++ return;
++
++ sqlite3_bind_text (stmt, 1, line, -1, SQLITE_STATIC);
++ sqlite3_bind_text (stmt, 2, line, -1, SQLITE_STATIC);
++ sqlite3_step (stmt);
++ sqlite3_finalize (stmt);
++}
++
++static void
++sqlite_history_clear ()
++{
++ if (hist_db == NULL)
++ return;
++ sqlite3_exec (hist_db, "DELETE FROM history", NULL, NULL, NULL);
++}
++
++/* ------------------------------------------------------------------ */
++/* Boilerplate identical to bashhist.c */
++/* ------------------------------------------------------------------ */
++
++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 struct ignorevar histignore =
++{
++ "HISTIGNORE",
++ (struct ign *)0,
++ 0,
++ (char *)0,
++ (sh_iv_item_func_t *)histignore_item_func,
++};
++
++#define HIGN_EXPAND 0x01
++
++int remember_on_history = 0;
++int enable_history_list = 0;
++int history_lines_this_session;
++int history_lines_in_file;
++
++#if defined (BANG_HISTORY)
++int history_expansion_inhibited;
++int double_quotes_inhibit_history_expansion = 0;
++#endif
++
++int command_oriented_history = 1;
++int current_command_first_line_saved = 0;
++int literal_history;
++int force_append_history;
++int history_control;
++int hist_last_line_added;
++int hist_last_line_pushed;
++
++#if defined (READLINE)
++int history_reediting;
++int hist_verify;
++#endif
++
++int dont_save_function_defs;
++
++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));
++#endif
++#if defined (READLINE)
++static void re_edit __P((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 *));
++
++#if defined (BANG_HISTORY)
++static int
++bash_history_inhibit_expansion (string, i)
++ char *string;
++ int i;
++{
++ int t;
++ char hx[2];
++
++ hx[0] = history_expansion_char;
++ hx[1] = '\0';
++
++ if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1))
++ return (1);
++ else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' &&
++ member ('}', string + i + 1))
++ return (1);
++ else if (i > 1 && string[i - 1] == '$' && string[i] == '!')
++ return (1);
++#if defined (EXTENDED_GLOB)
++ else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2))
++ return (1);
++#endif
++ else if ((t = skip_to_histexp (string, 0, hx, SD_NOJMP|SD_HISTEXP)) > 0)
++ {
++ while (t < i)
++ {
++ t = skip_to_histexp (string, t+1, hx, SD_NOJMP|SD_HISTEXP);
++ if (t <= 0)
++ return 0;
++ }
++ return (t > i);
++ }
++ else
++ return (0);
++}
++#endif
++
++void
++bash_initialize_history ()
++{
++ history_quotes_inhibit_expansion = 1;
++ history_search_delimiter_chars = ";&()|<>";
++#if defined (BANG_HISTORY)
++ history_inhibit_expansion_function = bash_history_inhibit_expansion;
++ sv_histchars ("histchars");
++#endif
++}
++
++void
++bash_history_reinit (interact)
++ int interact;
++{
++#if defined (BANG_HISTORY)
++ history_expansion = interact != 0;
++ history_expansion_inhibited = 1;
++ history_inhibit_expansion_function = bash_history_inhibit_expansion;
++#endif
++ remember_on_history = enable_history_list;
++}
++
++void
++bash_history_disable ()
++{
++ remember_on_history = 0;
++#if defined (BANG_HISTORY)
++ history_expansion_inhibited = 1;
++#endif
++}
++
++void
++bash_history_enable ()
++{
++ remember_on_history = enable_history_list = 1;
++#if defined (BANG_HISTORY)
++ history_expansion_inhibited = 0;
++ history_inhibit_expansion_function = bash_history_inhibit_expansion;
++#endif
++ sv_history_control ("HISTCONTROL");
++ sv_histignore ("HISTIGNORE");
++}
++
++/* ------------------------------------------------------------------ */
++/* Modified functions */
++/* ------------------------------------------------------------------ */
++
++void
++load_history ()
++{
++ char *hf;
++ int hsval, nread;
++
++ set_if_not ("HISTSIZE", "500");
++ sv_histsize ("HISTSIZE");
++
++ hf = get_string_value ("HISTFILE");
++
++ if (sqlite_history_open (hf) == 0)
++ {
++ char *hs = get_string_value ("HISTSIZE");
++ hsval = (hs && *hs) ? atoi (hs) : 500;
++ if (hsval <= 0) hsval = 500;
++
++ nread = sqlite_history_load (hsval);
++ if (nread >= 0)
++ {
++ history_lines_in_file = nread;
++ using_history ();
++ return;
++ }
++ /* SQLite open succeeded but load failed; fall through to plain text. */
++ }
++
++ set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE"));
++ sv_histsize ("HISTFILESIZE");
++
++ if (hf && *hf && file_exists (hf))
++ {
++ read_history (hf);
++ history_lines_in_file = history_lines_read_from_file;
++ using_history ();
++ }
++}
++
++void
++bash_clear_history ()
++{
++ clear_history ();
++ history_lines_this_session = 0;
++ sqlite_history_clear ();
++}
++
++int
++bash_delete_histent (i)
++ int i;
++{
++ HIST_ENTRY *discard;
++
++ discard = remove_history (i);
++ if (discard)
++ free_history_entry (discard);
++ history_lines_this_session--;
++
++ return 1;
++}
++
++int
++bash_delete_last_history ()
++{
++ register int i;
++ HIST_ENTRY **hlist, *histent;
++ int r;
++
++ hlist = history_list ();
++ if (hlist == NULL)
++ return 0;
++
++ for (i = 0; hlist[i]; i++)
++ ;
++ i--;
++
++ histent = history_get (history_base + i);
++ if (histent == NULL)
++ return 0;
++
++ r = bash_delete_histent (i);
++
++ if (where_history () > history_length)
++ history_set_pos (history_length);
++
++ return r;
++}
++
++#ifdef INCLUDE_UNUSED
++void
++save_history ()
++{
++ char *hf;
++ int r;
++
++ hf = get_string_value ("HISTFILE");
++ if (hf && *hf && file_exists (hf))
++ {
++ using_history ();
++ if (history_lines_this_session <= where_history () || force_append_history)
++ r = append_history (history_lines_this_session, hf);
++ else
++ r = write_history (hf);
++ sv_histsize ("HISTFILESIZE");
++ }
++}
++#endif
++
++int
++maybe_append_history (filename)
++ char *filename;
++{
++ /* Commands are written to the DB on every add; nothing to append. */
++ if (hist_db != NULL)
++ {
++ history_lines_this_session = 0;
++ return EXECUTION_SUCCESS;
++ }
++
++ /* Fallback: plain-text path (SQLite unavailable). */
++ int fd, result;
++ struct stat buf;
++
++ result = EXECUTION_SUCCESS;
++ if (history_lines_this_session > 0 && (history_lines_this_session <= where_history ()))
++ {
++ if (stat (filename, &buf) == -1 && errno == ENOENT)
++ {
++ fd = open (filename, O_WRONLY|O_CREAT, 0600);
++ if (fd < 0)
++ {
++ builtin_error (_("%s: cannot create: %s"), filename, strerror (errno));
++ return (EXECUTION_FAILURE);
++ }
++ close (fd);
++ }
++ result = append_history (history_lines_this_session, filename);
++ history_lines_in_file += history_lines_this_session;
++ history_lines_this_session = 0;
++ }
++ else
++ history_lines_this_session = 0;
++
++ return (result);
++}
++
++int
++maybe_save_shell_history ()
++{
++ int result;
++ char *hf;
++
++ /* Commands are written to the DB on every add; nothing to do at exit. */
++ if (hist_db != NULL)
++ {
++ history_lines_this_session = 0;
++ return 0;
++ }
++
++ /* Fallback: plain-text path (SQLite unavailable). */
++ result = 0;
++ if (history_lines_this_session > 0)
++ {
++ hf = get_string_value ("HISTFILE");
++ if (hf && *hf)
++ {
++ if (file_exists (hf) == 0)
++ {
++ int file;
++ file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600);
++ if (file != -1)
++ close (file);
++ }
++ using_history ();
++ if (history_lines_this_session <= where_history () || force_append_history)
++ {
++ result = append_history (history_lines_this_session, hf);
++ history_lines_in_file += history_lines_this_session;
++ }
++ else
++ {
++ result = write_history (hf);
++ history_lines_in_file = history_lines_written_to_file;
++ }
++ history_lines_this_session = 0;
++ sv_histsize ("HISTFILESIZE");
++ }
++ }
++ return (result);
++}
++
++#if defined (READLINE)
++static void
++re_edit (text)
++ char *text;
++{
++ if (bash_input.type == st_stdin)
++ bash_re_edit (text);
++}
++#endif
++
++static int
++history_expansion_p (line)
++ char *line;
++{
++ register char *s;
++
++ for (s = line; *s; s++)
++ if (*s == history_expansion_char || *s == history_subst_char)
++ return 1;
++ return 0;
++}
++
++char *
++pre_process_line (line, print_changes, addit)
++ char *line;
++ int print_changes, addit;
++{
++ char *history_value;
++ char *return_value;
++ int expanded;
++
++ return_value = line;
++ expanded = 0;
++
++# if defined (BANG_HISTORY)
++ if (!history_expansion_inhibited && history_expansion && history_expansion_p (line))
++ {
++ expanded = history_expand (line, &history_value);
++
++ if (expanded)
++ {
++ if (print_changes)
++ {
++ if (expanded < 0)
++ internal_error ("%s", history_value);
++#if defined (READLINE)
++ else if (hist_verify == 0 || expanded == 2)
++#else
++ else
++#endif
++ fprintf (stderr, "%s\n", history_value);
++ }
++
++ if (expanded < 0 || expanded == 2)
++ {
++# if defined (READLINE)
++ if (expanded == 2 && rl_dispatching == 0 && *history_value)
++# else
++ if (expanded == 2 && *history_value)
++# endif
++ maybe_add_history (history_value);
++
++ free (history_value);
++
++# if defined (READLINE)
++ if (history_reediting && expanded < 0 && rl_done)
++ re_edit (line);
++# endif
++ return ((char *)NULL);
++ }
++
++# if defined (READLINE)
++ if (hist_verify && expanded == 1)
++ {
++ re_edit (history_value);
++ free (history_value);
++ return ((char *)NULL);
++ }
++# endif
++ }
++
++ expanded = 1;
++ return_value = history_value;
++ }
++# endif /* BANG_HISTORY */
++
++ if (addit && remember_on_history && *return_value)
++ maybe_add_history (return_value);
++
++ return (return_value);
++}
++
++static int
++shell_comment (line)
++ char *line;
++{
++ char *p;
++
++ for (p = line; p && *p && whitespace (*p); p++)
++ ;
++ return (p && *p == '#');
++}
++
++static int
++check_history_control (line)
++ char *line;
++{
++ HIST_ENTRY *temp;
++ int r;
++
++ if (history_control == 0)
++ return 1;
++
++ if ((history_control & HC_IGNSPACE) && *line == ' ')
++ return 0;
++
++ if (history_control & HC_IGNDUPS)
++ {
++ using_history ();
++ temp = previous_history ();
++ r = (temp == 0 || STREQ (temp->line, line) == 0);
++ using_history ();
++ if (r == 0)
++ return r;
++ }
++
++ return 1;
++}
++
++static void
++hc_erasedups (line)
++ char *line;
++{
++ HIST_ENTRY *temp;
++ int r;
++
++ using_history ();
++ while (temp = previous_history ())
++ {
++ if (STREQ (temp->line, line))
++ {
++ r = where_history ();
++ temp = remove_history (r);
++ if (temp)
++ free_history_entry (temp);
++ }
++ }
++ using_history ();
++ /* Signal sqlite_history_add to erase DB duplicates after the insert. */
++ sqlite_pending_erasedups = 1;
++}
++
++void
++maybe_add_history (line)
++ char *line;
++{
++ hist_last_line_added = 0;
++
++ 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))
++ bash_add_history (line);
++ return;
++ }
++
++ current_command_first_line_saved = check_add_history (line, 0);
++}
++
++int
++check_add_history (line, force)
++ char *line;
++ int force;
++{
++ if (check_history_control (line) && history_should_ignore (line) == 0)
++ {
++ if (history_control & HC_ERASEDUPS)
++ hc_erasedups (line);
++
++ if (force)
++ {
++ really_add_history (line);
++ using_history ();
++ }
++ else
++ bash_add_history (line);
++ return 1;
++ }
++ return 0;
++}
++
++#if defined (SYSLOG_HISTORY)
++#define SYSLOG_MAXLEN 600
++
++extern char *shell_name;
++
++#ifndef OPENLOG_OPTS
++#define OPENLOG_OPTS 0
++#endif
++
++void
++bash_syslog_history (line)
++ const char *line;
++{
++ char trunc[SYSLOG_MAXLEN];
++ static int first = 1;
++
++ if (first)
++ {
++ openlog (shell_name, OPENLOG_OPTS, SYSLOG_FACILITY);
++ first = 0;
++ }
++
++ if (strlen(line) < SYSLOG_MAXLEN)
++ syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "HISTORY: PID=%d UID=%d %s", getpid(), current_user.uid, 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);
++ }
++}
++#endif
++
++void
++bash_add_history (line)
++ char *line;
++{
++ int add_it, offset, curlen;
++ HIST_ENTRY *current, *old;
++ char *chars_to_add, *new_line;
++
++ add_it = 1;
++ if (command_oriented_history && current_command_line_count > 1)
++ {
++ if ((parser_state & PST_HEREDOC) && literal_history && current_command_line_count > 2 && line[strlen (line) - 1] == '\n')
++ chars_to_add = "";
++ else
++ chars_to_add = literal_history ? "\n" : history_delimiting_chars (line);
++
++ using_history ();
++ current = previous_history ();
++
++ if (current)
++ {
++ curlen = strlen (current->line);
++
++ if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' &&
++ current->line[curlen - 2] != '\\')
++ {
++ current->line[curlen - 1] = '\0';
++ curlen--;
++ chars_to_add = "";
++ }
++
++ if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\n' && *chars_to_add == ';')
++ chars_to_add++;
++
++ new_line = (char *)xmalloc (1 + curlen + strlen (line) + strlen (chars_to_add));
++ sprintf (new_line, "%s%s%s", current->line, chars_to_add, line);
++ offset = where_history ();
++ old = replace_history_entry (offset, new_line, current->data);
++ free (new_line);
++
++ if (old)
++ free_history_entry (old);
++
++ add_it = 0;
++ }
++ }
++
++ if (add_it)
++ really_add_history (line);
++
++#if defined (SYSLOG_HISTORY)
++ bash_syslog_history (line);
++#endif
++
++ using_history ();
++}
++
++static void
++really_add_history (line)
++ char *line;
++{
++ hist_last_line_added = 1;
++ hist_last_line_pushed = 0;
++ add_history (line);
++ history_lines_this_session++;
++ sqlite_history_add (line);
++}
++
++int
++history_number ()
++{
++ using_history ();
++ return (remember_on_history ? history_base + where_history () : 1);
++}
++
++static int
++should_expand (s)
++ char *s;
++{
++ char *p;
++
++ for (p = s; p && *p; p++)
++ {
++ if (*p == '\\')
++ p++;
++ else if (*p == '&')
++ return 1;
++ }
++ return 0;
++}
++
++static int
++histignore_item_func (ign)
++ struct ign *ign;
++{
++ if (should_expand (ign->val))
++ ign->flags |= HIGN_EXPAND;
++ return (0);
++}
++
++void
++setup_history_ignore (varname)
++ char *varname;
++{
++ setup_ignore_patterns (&histignore);
++}
++
++static HIST_ENTRY *
++last_history_entry ()
++{
++ HIST_ENTRY *he;
++
++ using_history ();
++ he = previous_history ();
++ using_history ();
++ return he;
++}
++
++char *
++last_history_line ()
++{
++ HIST_ENTRY *he;
++
++ he = last_history_entry ();
++ if (he == 0)
++ return ((char *)NULL);
++ return he->line;
++}
++
++static char *
++expand_histignore_pattern (pat)
++ char *pat;
++{
++ HIST_ENTRY *phe;
++ char *ret;
++
++ phe = last_history_entry ();
++
++ if (phe == (HIST_ENTRY *)0)
++ return (savestring (pat));
++
++ ret = strcreplace (pat, '&', phe->line, 1);
++
++ return ret;
++}
++
++static int
++history_should_ignore (line)
++ char *line;
++{
++ register int i, match;
++ char *npat;
++
++ if (histignore.num_ignores == 0)
++ return 0;
++
++ for (i = match = 0; i < histignore.num_ignores; i++)
++ {
++ if (histignore.ignores[i].flags & HIGN_EXPAND)
++ npat = expand_histignore_pattern (histignore.ignores[i].val);
++ else
++ npat = histignore.ignores[i].val;
++
++ match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH;
++
++ if (histignore.ignores[i].flags & HIGN_EXPAND)
++ free (npat);
++
++ if (match)
++ break;
++ }
++
++ return match;
++}