aboutsummaryrefslogtreecommitdiff
path: root/readline/histfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'readline/histfile.c')
-rw-r--r--readline/histfile.c274
1 files changed, 240 insertions, 34 deletions
diff --git a/readline/histfile.c b/readline/histfile.c
index 30a6182..8749886 100644
--- a/readline/histfile.c
+++ b/readline/histfile.c
@@ -1,6 +1,6 @@
/* histfile.c - functions to manipulate the history file. */
-/* Copyright (C) 1989-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2015 Free Software Foundation, Inc.
This file contains the GNU History Library (History), a set of
routines for managing the text of previously typed lines.
@@ -35,6 +35,10 @@
#include <stdio.h>
+#if defined (HAVE_LIMITS_H)
+# include <limits.h>
+#endif
+
#include <sys/types.h>
#if ! defined (_MINIX) && defined (HAVE_SYS_FILE_H)
# include <sys/file.h>
@@ -99,12 +103,31 @@ extern int errno;
#include "rlshell.h"
#include "xmalloc.h"
+#if !defined (PATH_MAX)
+# define PATH_MAX 1024 /* default */
+#endif
+
/* If non-zero, we write timestamps to the history file in history_do_write() */
int history_write_timestamps = 0;
+/* Immediately after a call to read_history() or read_history_range(), this
+ will return the number of lines just read from the history file in that
+ call. */
+int history_lines_read_from_file = 0;
+
+/* Immediately after a call to write_history() or history_do_write(), this
+ will return the number of lines just written to the history file in that
+ call. This also works with history_truncate_file. */
+int history_lines_written_to_file = 0;
+
/* Does S look like the beginning of a history timestamp entry? Placeholder
for more extensive tests. */
-#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((s)[1]) )
+#define HIST_TIMESTAMP_START(s) (*(s) == history_comment_char && isdigit ((unsigned char)(s)[1]) )
+
+static char *history_backupfile PARAMS((const char *));
+static char *history_tempfile PARAMS((const char *));
+static int histfile_backup PARAMS((const char *, const char *));
+static int histfile_restore PARAMS((const char *, const char *));
/* Return the string that should be used in the place of this
filename. This only matters when you don't specify the
@@ -123,16 +146,13 @@ history_filename (filename)
return (return_val);
home = sh_get_env_value ("HOME");
-
+#if defined (_WIN32)
if (home == 0)
- {
-#if 0
- home = ".";
- home_len = 1;
-#else
- return (NULL);
+ home = sh_get_env_value ("APPDATA");
#endif
- }
+
+ if (home == 0)
+ return (NULL);
else
home_len = strlen (home);
@@ -148,6 +168,75 @@ history_filename (filename)
return (return_val);
}
+static char *
+history_backupfile (filename)
+ const char *filename;
+{
+ const char *fn;
+ char *ret, linkbuf[PATH_MAX+1];
+ size_t len;
+ ssize_t n;
+ struct stat fs;
+
+ fn = filename;
+#if defined (HAVE_READLINK)
+ /* Follow symlink to avoid backing up symlink itself; call will fail if
+ not a symlink */
+ if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0)
+ {
+ linkbuf[n] = '\0';
+ fn = linkbuf;
+ }
+#endif
+
+ len = strlen (fn);
+ ret = xmalloc (len + 2);
+ strcpy (ret, fn);
+ ret[len] = '-';
+ ret[len+1] = '\0';
+ return ret;
+}
+
+static char *
+history_tempfile (filename)
+ const char *filename;
+{
+ const char *fn;
+ char *ret, linkbuf[PATH_MAX+1];
+ size_t len;
+ ssize_t n;
+ struct stat fs;
+ int pid;
+
+ fn = filename;
+#if defined (HAVE_READLINK)
+ /* Follow symlink so tempfile created in the same directory as any symlinked
+ history file; call will fail if not a symlink */
+ if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0)
+ {
+ linkbuf[n] = '\0';
+ fn = linkbuf;
+ }
+#endif
+
+ len = strlen (fn);
+ ret = xmalloc (len + 11);
+ strcpy (ret, fn);
+
+ pid = (int)getpid ();
+
+ /* filename-PID.tmp */
+ ret[len] = '-';
+ ret[len+1] = (pid / 10000 % 10) + '0';
+ ret[len+2] = (pid / 1000 % 10) + '0';
+ ret[len+3] = (pid / 100 % 10) + '0';
+ ret[len+4] = (pid / 10 % 10) + '0';
+ ret[len+5] = (pid % 10) + '0';
+ strcpy (ret + len + 6, ".tmp");
+
+ return ret;
+}
+
/* Add the contents of FILENAME to the history list, a line at a time.
If FILENAME is NULL, then read from ~/.history. Returns 0 if
successful, or errno if not. */
@@ -181,6 +270,8 @@ read_history_range (filename, from, to)
int overflow_errno = EIO;
#endif
+ history_lines_read_from_file = 0;
+
buffer = last_ts = (char *)NULL;
input = history_filename (filename);
file = input ? open (input, O_RDONLY|O_BINARY, 0666) : -1;
@@ -293,6 +384,8 @@ read_history_range (filename, from, to)
line_start = line_end + 1;
}
+ history_lines_read_from_file = current_line;
+
FREE (input);
#ifndef HISTORY_USE_MMAP
FREE (buffer);
@@ -303,23 +396,68 @@ read_history_range (filename, from, to)
return (0);
}
+/* Save FILENAME to BACK, handling case where FILENAME is a symlink
+ (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */
+static int
+histfile_backup (filename, back)
+ const char *filename;
+ const char *back;
+{
+#if defined (HAVE_READLINK)
+ char linkbuf[PATH_MAX+1];
+ ssize_t n;
+
+ /* Follow to target of symlink to avoid renaming symlink itself */
+ if ((n = readlink (filename, linkbuf, sizeof (linkbuf) - 1)) > 0)
+ {
+ linkbuf[n] = '\0';
+ return (rename (linkbuf, back));
+ }
+#endif
+ return (rename (filename, back));
+}
+
+/* Restore ORIG from BACKUP handling case where ORIG is a symlink
+ (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */
+static int
+histfile_restore (backup, orig)
+ const char *backup;
+ const char *orig;
+{
+#if defined (HAVE_READLINK)
+ char linkbuf[PATH_MAX+1];
+ ssize_t n;
+
+ /* Follow to target of symlink to avoid renaming symlink itself */
+ if ((n = readlink (orig, linkbuf, sizeof (linkbuf) - 1)) > 0)
+ {
+ linkbuf[n] = '\0';
+ return (rename (backup, linkbuf));
+ }
+#endif
+ return (rename (backup, orig));
+}
+
/* Truncate the history file FNAME, leaving only LINES trailing lines.
- If FNAME is NULL, then use ~/.history. Returns 0 on success, errno
- on failure. */
+ If FNAME is NULL, then use ~/.history. Writes a new file and renames
+ it to the original name. Returns 0 on success, errno on failure. */
int
history_truncate_file (fname, lines)
const char *fname;
int lines;
{
- char *buffer, *filename, *bp, *bp1; /* bp1 == bp+1 */
- int file, chars_read, rv;
+ char *buffer, *filename, *tempname, *bp, *bp1; /* bp1 == bp+1 */
+ int file, chars_read, rv, orig_lines, exists;
struct stat finfo;
size_t file_size;
+ history_lines_written_to_file = 0;
+
buffer = (char *)NULL;
filename = history_filename (fname);
+ tempname = 0;
file = filename ? open (filename, O_RDONLY|O_BINARY, 0666) : -1;
- rv = 0;
+ rv = exists = 0;
/* Don't try to truncate non-regular files. */
if (file == -1 || fstat (file, &finfo) == -1)
@@ -329,6 +467,7 @@ history_truncate_file (fname, lines)
close (file);
goto truncate_exit;
}
+ exists = 1;
if (S_ISREG (finfo.st_mode) == 0)
{
@@ -360,6 +499,7 @@ history_truncate_file (fname, lines)
buffer = (char *)malloc (file_size + 1);
if (buffer == 0)
{
+ rv = errno;
close (file);
goto truncate_exit;
}
@@ -373,6 +513,7 @@ history_truncate_file (fname, lines)
goto truncate_exit;
}
+ orig_lines = lines;
/* Count backwards from the end of buffer until we have passed
LINES lines. bp1 is set funny initially. But since bp[1] can't
be a comment character (since it's off the end) and *bp can't be
@@ -401,27 +542,56 @@ history_truncate_file (fname, lines)
/* Write only if there are more lines in the file than we want to
truncate to. */
- if (bp > buffer && ((file = open (filename, O_WRONLY|O_TRUNC|O_BINARY, 0600)) != -1))
+ if (bp <= buffer)
{
- write (file, bp, chars_read - (bp - buffer));
+ rv = 0;
+ /* No-op if LINES == 0 at this point */
+ history_lines_written_to_file = orig_lines - lines;
+ goto truncate_exit;
+ }
-#if defined (__BEOS__)
- /* BeOS ignores O_TRUNC. */
- ftruncate (file, chars_read - (bp - buffer));
-#endif
+ tempname = history_tempfile (filename);
- close (file);
+ if ((file = open (tempname, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0600)) != -1)
+ {
+ if (write (file, bp, chars_read - (bp - buffer)) < 0)
+ rv = errno;
+
+ if (close (file) < 0 && rv == 0)
+ rv = errno;
}
+ else
+ rv = errno;
truncate_exit:
-
FREE (buffer);
+ history_lines_written_to_file = orig_lines - lines;
+
+ if (rv == 0 && filename && tempname)
+ rv = histfile_restore (tempname, filename);
+
+ if (rv != 0)
+ {
+ if (tempname)
+ unlink (tempname);
+ history_lines_written_to_file = 0;
+ }
+
+ /* Make sure the new filename is owned by the same user as the old. If one
+ user is running this, it's a no-op. If the shell is running after sudo
+ with a shared history file, we don't want to leave the history file
+ owned by root. */
+ if (rv == 0 && exists)
+ chown (filename, finfo.st_uid, finfo.st_gid);
+
xfree (filename);
+ FREE (tempname);
+
return rv;
}
-/* Workhorse function for writing history. Writes NELEMENT entries
+/* Workhorse function for writing history. Writes the last NELEMENT entries
from the history list to FILENAME. OVERWRITE is non-zero if you
wish to replace FILENAME with the entries. */
static int
@@ -430,23 +600,32 @@ history_do_write (filename, nelements, overwrite)
int nelements, overwrite;
{
register int i;
- char *output;
- int file, mode, rv;
+ char *output, *tempname, *histname;
+ int file, mode, rv, exists;
+ struct stat finfo;
#ifdef HISTORY_USE_MMAP
size_t cursize;
+ history_lines_written_to_file = 0;
+
mode = overwrite ? O_RDWR|O_CREAT|O_TRUNC|O_BINARY : O_RDWR|O_APPEND|O_BINARY;
#else
mode = overwrite ? O_WRONLY|O_CREAT|O_TRUNC|O_BINARY : O_WRONLY|O_APPEND|O_BINARY;
#endif
- output = history_filename (filename);
+ histname = history_filename (filename);
+ tempname = (overwrite && histname) ? history_tempfile (histname) : 0;
+ output = tempname ? tempname : histname;
+ exists = histname ? (stat (histname, &finfo) == 0) : 0;
+
file = output ? open (output, mode, 0600) : -1;
rv = 0;
if (file == -1)
{
- FREE (output);
- return (errno);
+ rv = errno;
+ FREE (histname);
+ FREE (tempname);
+ return (rv);
}
#ifdef HISTORY_USE_MMAP
@@ -486,8 +665,11 @@ history_do_write (filename, nelements, overwrite)
{
mmap_error:
rv = errno;
- FREE (output);
close (file);
+ if (tempname)
+ unlink (tempname);
+ FREE (histname);
+ FREE (tempname);
return rv;
}
#else
@@ -495,8 +677,11 @@ mmap_error:
if (buffer == 0)
{
rv = errno;
- FREE (output);
close (file);
+ if (tempname)
+ unlink (tempname);
+ FREE (histname);
+ FREE (tempname);
return rv;
}
#endif
@@ -515,7 +700,7 @@ mmap_error:
}
#ifdef HISTORY_USE_MMAP
- if (msync (buffer, buffer_size, 0) != 0 || munmap (buffer, buffer_size) != 0)
+ if (msync (buffer, buffer_size, MS_ASYNC) != 0 || munmap (buffer, buffer_size) != 0)
rv = errno;
#else
if (write (file, buffer, buffer_size) < 0)
@@ -524,9 +709,30 @@ mmap_error:
#endif
}
- close (file);
+ history_lines_written_to_file = nelements;
+
+ if (close (file) < 0 && rv == 0)
+ rv = errno;
+
+ if (rv == 0 && histname && tempname)
+ rv = histfile_restore (tempname, histname);
+
+ if (rv != 0)
+ {
+ if (tempname)
+ unlink (tempname);
+ history_lines_written_to_file = 0;
+ }
+
+ /* Make sure the new filename is owned by the same user as the old. If one
+ user is running this, it's a no-op. If the shell is running after sudo
+ with a shared history file, we don't want to leave the history file
+ owned by root. */
+ if (rv == 0 && exists)
+ chown (histname, finfo.st_uid, finfo.st_gid);
- FREE (output);
+ FREE (histname);
+ FREE (tempname);
return (rv);
}