]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - builtins/read.def
Bash-4.4 patch 18
[thirdparty/bash.git] / builtins / read.def
index fb4366febe3353c15c409c5372470bb19cbc048f..b54b3af6ce19dba6a5490b19da2c9261a20bef24 100644 (file)
@@ -1,7 +1,7 @@
 This file is read.def, from which is created read.c.
 It implements the builtin "read" in Bash.
 
-Copyright (C) 1987-2009 Free Software Foundation, Inc.
+Copyright (C) 1987-2015 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -22,7 +22,7 @@ $PRODUCES read.c
 
 $BUILTIN read
 $FUNCTION read_builtin
-$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
+$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
 Read a line from the standard input and split it into fields.
 
 Reads a single line from the standard input, or from file descriptor FD
@@ -39,24 +39,31 @@ Options:
                variable ARRAY, starting at zero
   -d delim     continue until the first character of DELIM is read, rather
                than newline
-  -e           use Readline to obtain the line in an interactive shell
-  -i text      Use TEXT as the initial text for Readline
+  -e   use Readline to obtain the line in an interactive shell
+  -i text      use TEXT as the initial text for Readline
   -n nchars    return after reading NCHARS characters rather than waiting
-               for a newline
+               for a newline, but honor a delimiter if fewer than
+               NCHARS characters are read before the delimiter
+  -N nchars    return only after reading exactly NCHARS characters, unless
+               EOF is encountered or read times out, ignoring any
+               delimiter
   -p prompt    output the string PROMPT without a trailing newline before
                attempting to read
-  -r           do not allow backslashes to escape any characters
-  -s           do not echo input coming from a terminal
-  -t timeout   time out and return failure if a complete line of input is
-               not read withint TIMEOUT seconds.  The value of the TMOUT
-               variable is the default timeout.  TIMEOUT may be a
-               fractional number.  If TIMEOUT is 0, read returns success only
-               if input is available on the specified file descriptor.  The
-               exit status is greater than 128 if the timeout is exceeded
-  -u fd                read from file descriptor FD instead of the standard input
+  -r   do not allow backslashes to escape any characters
+  -s   do not echo input coming from a terminal
+  -t timeout   time out and return failure if a complete line of
+               input is not read within TIMEOUT seconds.  The value of the
+               TMOUT variable is the default timeout.  TIMEOUT may be a
+               fractional number.  If TIMEOUT is 0, read returns
+               immediately, without trying to read any data, returning
+               success only if input is available on the specified
+               file descriptor.  The exit status is greater than 128
+               if the timeout is exceeded
+  -u fd        read from file descriptor FD instead of the standard input
 
 Exit Status:
-The return code is zero, unless end-of-file is encountered, read times out,
+The return code is zero, unless end-of-file is encountered, read times out
+(in which case it's greater than 128), a variable assignment error occurs,
 or an invalid file descriptor is supplied as the argument to -u.
 $END
 
@@ -98,10 +105,17 @@ $END
 #  include "input.h"
 #endif
 
+#include "shmbutil.h"
+
 #if !defined(errno)
 extern int errno;
 #endif
 
+extern void run_pending_traps __P((void));
+
+extern int posixly_correct;
+extern int trapped_signal_received;
+
 struct ttsave
 {
   int fd;
@@ -124,22 +138,36 @@ static void ttyrestore __P((struct ttsave *));
 static sighandler sigalrm __P((int));
 static void reset_alarm __P((void));
 
-static procenv_t alrmbuf;
+/* Try this to see what the rest of the shell can do with the information. */
+procenv_t alrmbuf;
+int sigalrm_seen;
+
+static int reading, tty_modified;
 static SigHandler *old_alrm;
 static unsigned char delim;
 
+static struct ttsave termsave;
+
+/* In all cases, SIGALRM just sets a flag that we check periodically.  This
+   avoids problems with the semi-tricky stuff we do with the xfree of
+   input_string at the top of the unwind-protect list (see below). */
+
+/* Set a flag that CHECK_ALRM can check.  This relies on zread calling
+   trap.c:check_signals_and_traps(), which knows about sigalrm_seen and
+   alrmbuf. */
 static sighandler
 sigalrm (s)
      int s;
 {
-  longjmp (alrmbuf, 1);
+  sigalrm_seen = 1;
 }
 
 static void
 reset_alarm ()
 {
-  set_signal_handler (SIGALRM, old_alrm);
+  /* Cancel alarm before restoring signal handler. */
   falarm (0, 0);
+  set_signal_handler (SIGALRM, old_alrm);
 }
 
 /* Read the value of the shell variables whose names follow.
@@ -153,9 +181,10 @@ read_builtin (list)
      WORD_LIST *list;
 {
   register char *varname;
-  int size, i, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2;
+  int size, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2;
+  volatile int i;
   int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul;
-  int raw, edit, nchars, silent, have_timeout, fd;
+  int raw, edit, nchars, silent, have_timeout, ignore_delim, fd, lastsig, t_errno;
   unsigned int tmsec, tmusec;
   long ival, uval;
   intmax_t intval;
@@ -165,7 +194,6 @@ read_builtin (list)
   struct stat tsb;
   SHELL_VAR *var;
   TTYSTRUCT ttattrs, ttset;
-  struct ttsave termsave;
 #if defined (ARRAY_VARS)
   WORD_LIST *alist;
 #endif
@@ -196,6 +224,9 @@ read_builtin (list)
 #endif
   USE_VAR(list);
   USE_VAR(ps2);
+  USE_VAR(lastsig);
+
+  sigalrm_seen = reading = tty_modified = 0;
 
   i = 0;               /* Index into the string that we are reading. */
   raw = edit = 0;      /* Not reading raw input by default. */
@@ -211,9 +242,10 @@ read_builtin (list)
   tmsec = tmusec = 0;          /* no timeout */
   nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0;
   delim = '\n';                /* read until newline */
+  ignore_delim = 0;
 
   reset_internal_getopt ();
-  while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:")) != -1)
+  while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1)
     {
       switch (opt)
        {
@@ -255,6 +287,9 @@ read_builtin (list)
              tmusec = uval;
            }
          break;
+       case 'N':
+         ignore_delim = 1;
+         delim = -1;
        case 'n':
          code = legal_number (list_optarg, &intval);
          if (code == 0 || intval < 0 || intval != (int)intval)
@@ -283,6 +318,7 @@ read_builtin (list)
        case 'd':
          delim = *list_optarg;
          break;
+       CASE_HELPOPT;
        default:
          builtin_usage ();
          return (EX_USAGE);
@@ -299,10 +335,29 @@ read_builtin (list)
     return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
 #endif
 
+  /* Convenience: check early whether or not the first of possibly several
+     variable names is a valid identifier, and bail early if so. */
+#if defined (ARRAY_VARS)
+  if (list && legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, 0) == 0)
+#else
+  if (list && legal_identifier (list->word->word) == 0)
+#endif
+    {
+      sh_invalidid (list->word->word);
+      return (EXECUTION_FAILURE);
+    }
+
+  /* If we're asked to ignore the delimiter, make sure we do. */
+  if (ignore_delim)
+    delim = -1;
+
   /* IF IFS is unset, we use the default of " \t\n". */
   ifs_chars = getifs ();
   if (ifs_chars == 0)          /* XXX - shouldn't happen */
     ifs_chars = "";
+  /* If we want to read exactly NCHARS chars, don't split on IFS */
+  if (ignore_delim)
+    ifs_chars = "";
   for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++)
     skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL;
 
@@ -366,23 +421,39 @@ read_builtin (list)
 
   if (tmsec > 0 || tmusec > 0)
     {
-      code = setjmp (alrmbuf);
+      code = setjmp_nosigs (alrmbuf);
       if (code)
        {
-#if 0
-         run_unwind_frame ("read_builtin");
-         return (EXECUTION_FAILURE);
-#else
+         sigalrm_seen = 0;
+         /* Tricky.  The top of the unwind-protect stack is the free of
+            input_string.  We want to run all the rest and use input_string,
+            so we have to save input_string temporarily, run the unwind-
+            protects, then restore input_string so we can use it later */
+         orig_input_string = 0;
          input_string[i] = '\0';       /* make sure it's terminated */
-         retval = 128+SIGALRM;;
+         if (i == 0)
+           {
+             t = (char *)xmalloc (1);
+             t[0] = 0;
+           }
+         else
+           t = savestring (input_string);
+
+         run_unwind_frame ("read_builtin");
+         input_string = t;
+         retval = 128+SIGALRM;
          goto assign_vars;
-#endif
        }
+      if (interactive_shell == 0)
+       initialize_terminating_signals ();
       old_alrm = set_signal_handler (SIGALRM, sigalrm);
       add_unwind_protect (reset_alarm, (char *)NULL);
 #if defined (READLINE)
       if (edit)
-       add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
+       {
+         add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
+         add_unwind_protect (bashline_reset_event_hook, (char *)NULL);
+       }
 #endif
       falarm (tmsec, tmusec);
     }
@@ -419,7 +490,10 @@ read_builtin (list)
          i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset);
          if (i < 0)
            sh_ttyerror (1);
+         tty_modified = 1;
          add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+         if (interactive_shell == 0)
+           initialize_terminating_signals ();
        }
     }
   else if (silent)     /* turn off echo but leave term in canonical mode */
@@ -434,16 +508,21 @@ read_builtin (list)
       if (i < 0)
        sh_ttyerror (1);
 
+      tty_modified = 1;
       add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+      if (interactive_shell == 0)
+       initialize_terminating_signals ();
     }
 
   /* This *must* be the top unwind-protect on the stack, so the manipulation
      of the unwind-protect stack after the realloc() works right. */
   add_unwind_protect (xfree, input_string);
-  interrupt_immediately++;
-  terminate_immediately++;
 
-  unbuffered_read = (nchars > 0) || (delim != '\n') || input_is_pipe;
+  CHECK_ALRM;
+  if ((nchars > 0) && (input_is_tty == 0) && ignore_delim)     /* read -N */
+    unbuffered_read = 2;
+  else if ((nchars > 0) || (delim != '\n') || input_is_pipe)
+    unbuffered_read = 1;
 
   if (prompt && edit == 0)
     {
@@ -458,6 +537,8 @@ read_builtin (list)
   ps2 = 0;
   for (print_ps2 = eof = retval = 0;;)
     {
+      CHECK_ALRM;
+
 #if defined (READLINE)
       if (edit)
        {
@@ -468,7 +549,9 @@ read_builtin (list)
            }
          if (rlbuf == 0)
            {
+             reading = 1;
              rlbuf = edit_line (prompt ? prompt : "", itext);
+             reading = 0;
              rlind = 0;
            }
          if (rlbuf == 0)
@@ -491,26 +574,60 @@ read_builtin (list)
          print_ps2 = 0;
        }
 
-      if (unbuffered_read)
-       retval = zread (fd, &c, 1);
+#if 0
+      if (posixly_correct == 0)
+       interrupt_immediately++;
+#endif
+      reading = 1;
+      if (unbuffered_read == 2)
+       retval = posixly_correct ? zreadintr (fd, &c, 1) : zreadn (fd, &c, nchars - nr);
+      else if (unbuffered_read)
+       retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1);
       else
-       retval = zreadc (fd, &c);
+       retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c);
+      reading = 0;
+#if 0
+      if (posixly_correct == 0)
+       interrupt_immediately--;
+#endif
 
       if (retval <= 0)
        {
+         if (retval < 0 && errno == EINTR)
+           {
+             lastsig = LASTSIG();
+             if (lastsig == 0)
+               lastsig = trapped_signal_received;
+             run_pending_traps ();     /* because interrupt_immediately is not set */
+           }
+         else
+           lastsig = 0;
+         if (terminating_signal && tty_modified)
+           ttyrestore (&termsave);     /* fix terminal before exiting */
+         CHECK_TERMSIG;
          eof = 1;
          break;
        }
 
+      CHECK_ALRM;
+      QUIT;            /* in case we didn't call check_signals() */
 #if defined (READLINE)
        }
 #endif
 
+      CHECK_ALRM;
       if (i + 4 >= size)       /* XXX was i + 2; use i + 4 for multibyte/read_mbchar */
        {
-         input_string = (char *)xrealloc (input_string, size += 128);
-         remove_unwind_protect ();
-         add_unwind_protect (xfree, input_string);
+         char *t;
+         t = (char *)xrealloc (input_string, size += 128);
+
+         /* Only need to change unwind-protect if input_string changes */
+         if (t != input_string)
+           {
+             input_string = t;
+             remove_unwind_protect ();
+             add_unwind_protect (xfree, input_string);
+           }
        }
 
       /* If the next character is to be accepted verbatim, a backslash
@@ -541,9 +658,12 @@ read_builtin (list)
          continue;
        }
 
-      if ((unsigned char)c == delim)
+      if (ignore_delim == 0 && (unsigned char)c == delim)
        break;
 
+      if (c == '\0' && delim != '\0')
+       continue;               /* skip NUL bytes in input */
+
       if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL))
        {
          saw_escape++;
@@ -552,9 +672,10 @@ read_builtin (list)
 
 add_char:
       input_string[i++] = c;
+      CHECK_ALRM;
 
 #if defined (HANDLE_MULTIBYTE)
-      if (nchars > 0 && MB_CUR_MAX > 1)
+      if (nchars > 0 && MB_CUR_MAX > 1 && is_basic (c) == 0)
        {
          input_string[i] = '\0';       /* for simplicity and debugging */
          i += read_mbchar (fd, input_string, i, c, unbuffered_read);
@@ -567,15 +688,21 @@ add_char:
        break;
     }
   input_string[i] = '\0';
+  CHECK_ALRM;
+
+#if defined (READLINE)
+  if (edit)
+    free (rlbuf);
+#endif
 
-#if 1
   if (retval < 0)
     {
-      builtin_error (_("read error: %d: %s"), fd, strerror (errno));
+      t_errno = errno;
+      if (errno != EINTR)
+       builtin_error (_("read error: %d: %s"), fd, strerror (errno));
       run_unwind_frame ("read_builtin");
-      return (EXECUTION_FAILURE);
+      return ((t_errno != EINTR) ? EXECUTION_FAILURE : 128+lastsig);
     }
-#endif
 
   if (tmsec > 0 || tmusec > 0)
     reset_alarm ();
@@ -601,8 +728,6 @@ add_char:
   if (unbuffered_read == 0)
     zsyncfd (fd);
 
-  interrupt_immediately--;
-  terminate_immediately--;
   discard_unwind_frame ("read_builtin");
 
   retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
@@ -627,6 +752,14 @@ assign_vars:
          xfree (input_string);
          return EXECUTION_FAILURE;     /* readonly or noassign */
        }
+      if (assoc_p (var))
+       {
+          builtin_error (_("%s: cannot convert associative to indexed array"), arrayname);
+         xfree (input_string);
+         return EXECUTION_FAILURE;     /* existing associative array */
+       }
+      else if (invisible_p (var))
+       VUNSETATTR (var, att_invisible);
       array_flush (array_cell (var));
 
       alist = list_string (input_string, ifs_chars, 0);
@@ -670,9 +803,12 @@ assign_vars:
        }
       else
        var = bind_variable ("REPLY", input_string, 0);
-      VUNSETATTR (var, att_invisible);
+      if (var == 0 || readonly_p (var) || noassign_p (var))
+       retval = EXECUTION_FAILURE;
+      else
+       VUNSETATTR (var, att_invisible);
 
-      free (input_string);
+      xfree (input_string);
       return (retval);
     }
 
@@ -689,7 +825,7 @@ assign_vars:
     {
       varname = list->word->word;
 #if defined (ARRAY_VARS)
-      if (legal_identifier (varname) == 0 && valid_array_reference (varname) == 0)
+      if (legal_identifier (varname) == 0 && valid_array_reference (varname, 0) == 0)
 #else
       if (legal_identifier (varname) == 0)
 #endif
@@ -716,7 +852,7 @@ assign_vars:
              xfree (t1);
            }
          else
-           var = bind_read_variable (varname, t);
+           var = bind_read_variable (varname, t ? t : "");
        }
       else
        {
@@ -737,7 +873,7 @@ assign_vars:
 
   /* Now assign the rest of the line to the last variable argument. */
 #if defined (ARRAY_VARS)
-  if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word) == 0)
+  if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, 0) == 0)
 #else
   if (legal_identifier (list->word->word) == 0)
 #endif
@@ -763,23 +899,31 @@ assign_vars:
       if (*input_string == 0)
        tofree = input_string = t;
       else
-       input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape);
+       {
+         input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape);
+         tofree = t;
+       }
     }
 #endif
 
-  if (saw_escape)
+  if (saw_escape && input_string && *input_string)
     {
       t = dequote_string (input_string);
       var = bind_read_variable (list->word->word, t);
       xfree (t);
     }
   else
-    var = bind_read_variable (list->word->word, input_string);
-  stupidly_hack_special_variables (list->word->word);
-  FREE (tofree);
+    var = bind_read_variable (list->word->word, input_string ? input_string : "");
 
   if (var)
-    VUNSETATTR (var, att_invisible);
+    {
+      stupidly_hack_special_variables (list->word->word);
+      VUNSETATTR (var, att_invisible);
+    }
+  else
+    retval = EXECUTION_FAILURE;
+
+  FREE (tofree);
   xfree (orig_input_string);
 
   return (retval);
@@ -789,14 +933,18 @@ static SHELL_VAR *
 bind_read_variable (name, value)
      char *name, *value;
 {
+  SHELL_VAR *v;
+
 #if defined (ARRAY_VARS)
-  if (valid_array_reference (name) == 0)
-    return (bind_variable (name, value, 0));
+  if (valid_array_reference (name, 0) == 0)
+    v = bind_variable (name, value, 0);
   else
-    return (assign_array_element (name, value, 0));
+    v = assign_array_element (name, value, 0);
 #else /* !ARRAY_VARS */
-  return bind_variable (name, value, 0);
+  v = bind_variable (name, value, 0);
 #endif /* !ARRAY_VARS */
+  return (v == 0 ? v
+                : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v));
 }
 
 #if defined (HANDLE_MULTIBYTE)
@@ -825,6 +973,7 @@ read_mbchar (fd, string, ind, ch, unbuffered)
       if (ret == (size_t)-2)
        {
          ps = ps_back;
+         /* We don't want to be interrupted during a multibyte char read */
          if (unbuffered)
            r = zread (fd, &c, 1);
          else
@@ -853,6 +1002,20 @@ ttyrestore (ttp)
      struct ttsave *ttp;
 {
   ttsetattr (ttp->fd, ttp->attrs);
+  tty_modified = 0;
+}
+
+void
+read_tty_cleanup ()
+{
+  if (tty_modified)
+    ttyrestore (&termsave);
+}
+
+int
+read_tty_modified ()
+{
+  return (tty_modified);
 }
 
 #if defined (READLINE)
@@ -899,15 +1062,19 @@ edit_line (p, itext)
 
   old_attempted_completion_function = rl_attempted_completion_function;
   rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+  bashline_set_event_hook ();
   if (itext)
     {
       old_startup_hook = rl_startup_hook;
       rl_startup_hook = set_itext;
       deftext = itext;
     }
+
   ret = readline (p);
+
   rl_attempted_completion_function = old_attempted_completion_function;
   old_attempted_completion_function = (rl_completion_func_t *)NULL;
+  bashline_reset_event_hook ();
 
   if (ret == 0)
     return ret;