Imported from ../bash-4.0-rc1.tar.gz.
[platform/upstream/bash.git] / lib / readline / histfile.c
index c0582de..a75fc16 100644 (file)
@@ -1,30 +1,34 @@
 /* histfile.c - functions to manipulate the history file. */
 
-/* Copyright (C) 1989, 1992 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2009 Free Software Foundation, Inc.
 
-   This file contains the GNU History Library (the Library), a set of
+   This file contains the GNU History Library (History), a set of
    routines for managing the text of previously typed lines.
 
-   The Library is free software; you can redistribute it and/or modify
+   History is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   The Library is distributed in the hope that it will be useful, but
-   WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-   General Public License for more details.
+   History is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   The GNU General Public License is often shipped with GNU software, and
-   is generally kept in a file called COPYING or LICENSE.  If you do not
-   have a copy of the license, write to the Free Software Foundation,
-   59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with History.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 /* The goal is to make the implementation transparent, so that you
    don't have to know what data types are used, just what functions
    you can call.  I think I have done that. */
+
 #define READLINE_LIBRARY
 
+#if defined (__TANDEM)
+#  include <floss.h>
+#endif
+
 #if defined (HAVE_CONFIG_H)
 #  include <config.h>
 #endif
@@ -32,7 +36,7 @@
 #include <stdio.h>
 
 #include <sys/types.h>
-#ifndef _MINIX
+#if ! defined (_MINIX) && defined (HAVE_SYS_FILE_H)
 #  include <sys/file.h>
 #endif
 #include "posixstat.h"
 #  include <unistd.h>
 #endif
 
-#if defined (HAVE_STRING_H)
-#  include <string.h>
-#else
-#  include <strings.h>
-#endif /* !HAVE_STRING_H */
+#include <ctype.h>
+
+#if defined (__EMX__)
+#  undef HAVE_MMAP
+#endif
+
+#ifdef HISTORY_USE_MMAP
+#  include <sys/mman.h>
+
+#  ifdef MAP_FILE
+#    define MAP_RFLAGS (MAP_FILE|MAP_PRIVATE)
+#    define MAP_WFLAGS (MAP_FILE|MAP_SHARED)
+#  else
+#    define MAP_RFLAGS MAP_PRIVATE
+#    define MAP_WFLAGS MAP_SHARED
+#  endif
 
+#  ifndef MAP_FAILED
+#    define MAP_FAILED ((void *)-1)
+#  endif
+
+#endif /* HISTORY_USE_MMAP */
 
 /* If we're compiling for __EMX__ (OS/2) or __CYGWIN__ (cygwin32 environment
    on win 95/98/nt), we want to open files with O_BINARY mode so that there
@@ -79,6 +99,13 @@ extern int errno;
 #include "rlshell.h"
 #include "xmalloc.h"
 
+/* If non-zero, we write timestamps to the history file in history_do_write() */
+int history_write_timestamps = 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]) )
+
 /* Return the string that should be used in the place of this
    filename.  This only matters when you don't specify the
    filename to read_history (), or write_history (). */
@@ -105,7 +132,7 @@ history_filename (filename)
   else
     home_len = strlen (home);
 
-  return_val = xmalloc (2 + home_len + 8); /* strlen(".history") == 8 */
+  return_val = (char *)xmalloc (2 + home_len + 8); /* strlen(".history") == 8 */
   strcpy (return_val, home);
   return_val[home_len] = '/';
 #if defined (__MSDOS__)
@@ -137,13 +164,20 @@ read_history_range (filename, from, to)
      const char *filename;
      int from, to;
 {
-  register int line_start, line_end;
-  char *input, *buffer;
+  register char *line_start, *line_end, *p;
+  char *input, *buffer, *bufend, *last_ts;
   int file, current_line, chars_read;
   struct stat finfo;
   size_t file_size;
+#if defined (EFBIG)
+  int overflow_errno = EFBIG;
+#elif defined (EOVERFLOW)
+  int overflow_errno = EOVERFLOW;
+#else
+  int overflow_errno = EIO;
+#endif
 
-  buffer = (char *)NULL;
+  buffer = last_ts = (char *)NULL;
   input = history_filename (filename);
   file = open (input, O_RDONLY|O_BINARY, 0666);
 
@@ -155,25 +189,46 @@ read_history_range (filename, from, to)
   /* check for overflow on very large files */
   if (file_size != finfo.st_size || file_size + 1 < file_size)
     {
-#if defined (EFBIG)
-      errno = EFBIG;
-#endif
+      errno = overflow_errno;
       goto error_and_exit;
     }
 
-  buffer = xmalloc (file_size + 1);
+#ifdef HISTORY_USE_MMAP
+  /* We map read/write and private so we can change newlines to NULs without
+     affecting the underlying object. */
+  buffer = (char *)mmap (0, file_size, PROT_READ|PROT_WRITE, MAP_RFLAGS, file, 0);
+  if ((void *)buffer == MAP_FAILED)
+    {
+      errno = overflow_errno;
+      goto error_and_exit;
+    }
+  chars_read = file_size;
+#else
+  buffer = (char *)malloc (file_size + 1);
+  if (buffer == 0)
+    {
+      errno = overflow_errno;
+      goto error_and_exit;
+    }
 
   chars_read = read (file, buffer, file_size);
+#endif
   if (chars_read < 0)
     {
   error_and_exit:
+      if (errno != 0)
+       chars_read = errno;
+      else
+       chars_read = EIO;
       if (file >= 0)
        close (file);
 
       FREE (input);
+#ifndef HISTORY_USE_MMAP
       FREE (buffer);
+#endif
 
-      return (errno);
+      return (chars_read);
     }
 
   close (file);
@@ -183,29 +238,48 @@ read_history_range (filename, from, to)
     to = chars_read;
 
   /* Start at beginning of file, work to end. */
-  line_start = line_end = current_line = 0;
+  bufend = buffer + chars_read;
+  current_line = 0;
 
   /* Skip lines until we are at FROM. */
-  while (line_start < chars_read && current_line < from)
-    {
-      for (line_end = line_start; line_end < chars_read; line_end++)
-       if (buffer[line_end] == '\n')
-         {
-           current_line++;
-           line_start = line_end + 1;
-           if (current_line == from)
-             break;
-         }
-    }
+  for (line_start = line_end = buffer; line_end < bufend && current_line < from; line_end++)
+    if (*line_end == '\n')
+      {
+       p = line_end + 1;
+       /* If we see something we think is a timestamp, continue with this
+          line.  We should check more extensively here... */
+       if (HIST_TIMESTAMP_START(p) == 0)
+         current_line++;
+       line_start = p;
+      }
 
   /* If there are lines left to gobble, then gobble them now. */
-  for (line_end = line_start; line_end < chars_read; line_end++)
-    if (buffer[line_end] == '\n')
+  for (line_end = line_start; line_end < bufend; line_end++)
+    if (*line_end == '\n')
       {
-       buffer[line_end] = '\0';
+       /* Change to allow Windows-like \r\n end of line delimiter. */
+       if (line_end > line_start && line_end[-1] == '\r')
+         line_end[-1] = '\0';
+       else
+         *line_end = '\0';
 
-       if (buffer[line_start])
-         add_history (buffer + line_start);
+       if (*line_start)
+         {
+           if (HIST_TIMESTAMP_START(line_start) == 0)
+             {
+               add_history (line_start);
+               if (last_ts)
+                 {
+                   add_history_time (last_ts);
+                   last_ts = NULL;
+                 }
+             }
+           else
+             {
+               last_ts = line_start;
+               current_line--;
+             }
+         }
 
        current_line++;
 
@@ -216,7 +290,11 @@ read_history_range (filename, from, to)
       }
 
   FREE (input);
+#ifndef HISTORY_USE_MMAP
   FREE (buffer);
+#else
+  munmap (buffer, file_size);
+#endif
 
   return (0);
 }
@@ -229,9 +307,8 @@ history_truncate_file (fname, lines)
      const char *fname;
      int lines;
 {
-  register int i;
+  char *buffer, *filename, *bp, *bp1;          /* bp1 == bp+1 */
   int file, chars_read, rv;
-  char *buffer, *filename;
   struct stat finfo;
   size_t file_size;
 
@@ -276,7 +353,13 @@ history_truncate_file (fname, lines)
       goto truncate_exit;
     }
 
-  buffer = xmalloc (file_size + 1);
+  buffer = (char *)malloc (file_size + 1);
+  if (buffer == 0)
+    {
+      close (file);
+      goto truncate_exit;
+    }
+
   chars_read = read (file, buffer, file_size);
   close (file);
 
@@ -287,11 +370,14 @@ history_truncate_file (fname, lines)
     }
 
   /* Count backwards from the end of buffer until we have passed
-     LINES lines. */
-  for (i = chars_read - 1; lines && i; i--)
+     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
+     both a newline and the history comment character, it should be OK. */
+  for (bp1 = bp = buffer + chars_read - 1; lines && bp > buffer; bp--)
     {
-      if (buffer[i] == '\n')
+      if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0)
        lines--;
+      bp1 = bp;
     }
 
   /* If this is the first line, then the file contains exactly the
@@ -299,22 +385,25 @@ history_truncate_file (fname, lines)
      anything.  It's the first line if we don't find a newline between
      the current value of i and 0.  Otherwise, write from the start of
      this line until the end of the buffer. */
-  for ( ; i; i--)
-    if (buffer[i] == '\n')
-      {
-       i++;
-       break;
-      }
+  for ( ; bp > buffer; bp--)
+    {
+      if (*bp == '\n' && HIST_TIMESTAMP_START(bp1) == 0)
+        {
+         bp++;
+         break;
+        }
+      bp1 = bp;
+    }
 
   /* Write only if there are more lines in the file than we want to
      truncate to. */
-  if (i && ((file = open (filename, O_WRONLY|O_TRUNC|O_BINARY, 0600)) != -1))
+  if (bp > buffer && ((file = open (filename, O_WRONLY|O_TRUNC|O_BINARY, 0600)) != -1))
     {
-      write (file, buffer + i, chars_read - i);
+      write (file, bp, chars_read - (bp - buffer));
 
 #if defined (__BEOS__)
       /* BeOS ignores O_TRUNC. */
-      ftruncate (file, chars_read - i);
+      ftruncate (file, chars_read - (bp - buffer));
 #endif
 
       close (file);
@@ -339,8 +428,13 @@ history_do_write (filename, nelements, overwrite)
   register int i;
   char *output;
   int file, mode, rv;
+#ifdef HISTORY_USE_MMAP
+  size_t cursize;
 
+  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);
   rv = 0;
 
@@ -350,6 +444,10 @@ history_do_write (filename, nelements, overwrite)
       return (errno);
     }
 
+#ifdef HISTORY_USE_MMAP
+  cursize = overwrite ? 0 : lseek (file, 0, SEEK_END);
+#endif
+
   if (nelements > history_length)
     nelements = history_length;
 
@@ -364,21 +462,61 @@ history_do_write (filename, nelements, overwrite)
     the_history = history_list ();
     /* Calculate the total number of bytes to write. */
     for (buffer_size = 0, i = history_length - nelements; i < history_length; i++)
-      buffer_size += 1 + strlen (the_history[i]->line);
+#if 0
+      buffer_size += 2 + HISTENT_BYTES (the_history[i]);
+#else
+      {
+       if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0])
+         buffer_size += strlen (the_history[i]->timestamp) + 1;
+       buffer_size += strlen (the_history[i]->line) + 1;
+      }
+#endif
 
     /* Allocate the buffer, and fill it. */
-    buffer = xmalloc (buffer_size);
+#ifdef HISTORY_USE_MMAP
+    if (ftruncate (file, buffer_size+cursize) == -1)
+      goto mmap_error;
+    buffer = (char *)mmap (0, buffer_size, PROT_READ|PROT_WRITE, MAP_WFLAGS, file, cursize);
+    if ((void *)buffer == MAP_FAILED)
+      {
+mmap_error:
+       rv = errno;
+       FREE (output);
+       close (file);
+       return rv;
+      }
+#else    
+    buffer = (char *)malloc (buffer_size);
+    if (buffer == 0)
+      {
+       rv = errno;
+       FREE (output);
+       close (file);
+       return rv;
+      }
+#endif
 
     for (j = 0, i = history_length - nelements; i < history_length; i++)
       {
+       if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0])
+         {
+           strcpy (buffer + j, the_history[i]->timestamp);
+           j += strlen (the_history[i]->timestamp);
+           buffer[j++] = '\n';
+         }
        strcpy (buffer + j, the_history[i]->line);
        j += strlen (the_history[i]->line);
        buffer[j++] = '\n';
       }
 
+#ifdef HISTORY_USE_MMAP
+    if (msync (buffer, buffer_size, 0) != 0 || munmap (buffer, buffer_size) != 0)
+      rv = errno;
+#else
     if (write (file, buffer, buffer_size) < 0)
       rv = errno;
     free (buffer);
+#endif
   }
 
   close (file);