You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2013/01/27 00:56:16 UTC

svn commit: r1438998 - in /subversion/trunk: configure.ac subversion/libsvn_subr/prompt.c

Author: brane
Date: Sat Jan 26 23:56:16 2013
New Revision: 1438998

URL: http://svn.apache.org/viewvc?rev=1438998&view=rev
Log:
Fix issue #4280: Prompt the controlling terminal, rather than stdin.

* configure.ac: Check for TERMIOS support.
* subversion/libsvn_subr/prompt.c:
   Include apr_portable.h and optionally either conio.h or termios.h.
 (terminal_handle_t): New struct, open terminal descriptor.
 (terminal_cleanup_handler, terminal_plain_cleanup, terminal_cleanup_handler):
  New; pool cleanup handlers for closing an open terminal.
 (terminal_close): New; explicitly closes a terminal.
 (terminal_open): New; opens and initializes a terminal.
 (terminal_puts, terminal_getc): New; terminal I/O uses direct console
  I/O on Windows and/or /dev/tty where available, with fallback to
  stdin for input and stderr for prompt/output.
 (prompt): Use the new terminal functions to implement platform-
  -independent prompting and user input.
 (maybe_print_realm, plaintext_prompt_helper):
  Use terminal I/O to display prompts.
  (wait_for_input): Removed; equivalent functionality moved to
   terminal_open and terminal_close.

Modified:
    subversion/trunk/configure.ac
    subversion/trunk/subversion/libsvn_subr/prompt.c

Modified: subversion/trunk/configure.ac
URL: http://svn.apache.org/viewvc/subversion/trunk/configure.ac?rev=1438998&r1=1438997&r2=1438998&view=diff
==============================================================================
--- subversion/trunk/configure.ac (original)
+++ subversion/trunk/configure.ac Sat Jan 26 23:56:16 2013
@@ -883,6 +883,13 @@ AC_CHECK_FUNCS(symlink readlink)
 dnl check for uname
 AC_CHECK_HEADERS(sys/utsname.h, [AC_CHECK_FUNCS(uname)], [])
 
+dnl check for termios
+AC_CHECK_HEADER(termios.h,[
+  AC_CHECK_FUNCS(tcgetattr tcsetattr,[
+    AC_DEFINE(HAVE_TERMIOS_H,1,[Defined if we have a usable termios library.])
+  ])
+])
+
 dnl Process some configuration options ----------
 
 AC_ARG_WITH(ssl,

Modified: subversion/trunk/subversion/libsvn_subr/prompt.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/prompt.c?rev=1438998&r1=1438997&r2=1438998&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/prompt.c (original)
+++ subversion/trunk/subversion/libsvn_subr/prompt.c Sat Jan 26 23:56:16 2013
@@ -29,6 +29,7 @@
 
 #include <apr_lib.h>
 #include <apr_poll.h>
+#include <apr_portable.h>
 
 #include "svn_cmdline.h"
 #include "svn_string.h"
@@ -39,41 +40,331 @@
 #include "private/svn_cmdline_private.h"
 #include "svn_private_config.h"
 
+#ifdef WIN32
+#include <conio.h>
+#elif defined(HAVE_TERMIOS_H)
+#include <termios.h>
+#endif
+
 
 
-/* Wait for input on @a *f.  Doing all allocations
- * in @a pool.  This functions is based on apr_wait_for_io_or_timeout().
- * Note that this will return an EINTR on a signal.
- *
- * ### FIX: When APR gives us a better way of doing this use it. */
-static apr_status_t wait_for_input(apr_file_t *f,
-                                   apr_pool_t *pool)
-{
-#ifndef WIN32
-  apr_pollfd_t pollset;
-  int srv, n;
-
-  pollset.desc_type = APR_POLL_FILE;
-  pollset.desc.f = f;
-  pollset.p = pool;
-  pollset.reqevents = APR_POLLIN;
-
-  srv = apr_poll(&pollset, 1, &n, -1);
-
-  if (n == 1 && pollset.rtnevents & APR_POLLIN)
-    return APR_SUCCESS;
-
-  return srv;
-#else
-  /* APR specs say things that are unimplemented are supposed to return
-   * APR_ENOTIMPL.  But when trying to use APR_POLL_FILE with apr_poll
-   * on Windows it returns APR_EBADF instead.  So just return APR_ENOTIMPL
-   * ourselves here.
-   */
-  return APR_ENOTIMPL;
+/* Descriptor of an open terminal */
+typedef struct terminal_handle_t terminal_handle_t;
+struct terminal_handle_t
+{
+  apr_file_t *infd;              /* input file handle */
+  apr_file_t *outfd;             /* output file handle */
+  svn_boolean_t noecho;          /* terminal echo was turned off */
+  svn_boolean_t close_handles;   /* close handles when closing the terminal */
+  apr_pool_t *pool;              /* pool associated with the file handles */
+
+#ifdef HAVE_TERMIOS_H
+  apr_os_file_t osinfd;          /* OS-specific handle for infd */
+  struct termios attr;           /* saved terminal attributes */
+#endif
+};
+
+
+/* Common pool cleanup handler for terminal_handle_t.
+   Closes TERMINAL. If RESTORE_ECHO_STATE is TRUE,
+   restores the echo flags of the terminal. */
+static apr_status_t
+terminal_cleanup_handler(terminal_handle_t *terminal,
+                         svn_boolean_t restore_echo_state)
+{
+  apr_status_t status = APR_SUCCESS;
+
+#ifdef HAVE_TERMIOS_H
+  if (restore_echo_state)
+    {
+      /* Restore terminal echo settings. */
+      if (terminal->noecho)
+        tcsetattr(terminal->osinfd, TCSANOW, &terminal->attr);
+    }
 #endif
+
+  if (terminal->close_handles)
+    {
+      if (terminal->infd)
+        status = apr_file_close(terminal->infd);
+
+      if (!status && terminal->outfd && terminal->outfd != terminal->infd)
+        status = apr_file_close(terminal->outfd);
+    }
+
+  return status;
+}
+
+/* Normal pool cleanup for a terminal. */
+static apr_status_t terminal_plain_cleanup(void *baton)
+{
+  return terminal_cleanup_handler(baton, TRUE);
+}
+
+/* Child pool cleanup for a terminal -- does not restore echo state. */
+static apr_status_t terminal_child_cleanup(void *baton)
+{
+  return terminal_cleanup_handler(baton, FALSE);
+}
+
+/* Explicitly close the terminal by running its registered
+   cleanup handlers. */
+static svn_error_t *
+terminal_close(terminal_handle_t *terminal)
+{
+  apr_status_t status;
+
+  /* Can't use apr_pool_cleanup_kill to remove the child cleanup; so
+     instead, replace it with the no-op handler. */
+  apr_pool_child_cleanup_set(terminal->pool, terminal,
+                             terminal_plain_cleanup,
+                             apr_pool_cleanup_null);
+
+  status = apr_pool_cleanup_run(terminal->pool, terminal,
+                                terminal_plain_cleanup);
+  if (status)
+    return svn_error_create(status, NULL, _("Can't close terminal"));
+  return SVN_NO_ERROR;
+}
+
+/* Open TERMINAL. If NOECHO is TRUE, try to turn off terminal echo.
+   Use POOL for al allocations.*/
+static svn_error_t *
+terminal_open(terminal_handle_t *terminal, svn_boolean_t noecho,
+              apr_pool_t *pool)
+{
+  apr_status_t status;
+
+#ifdef WIN32
+  /* On Windows, we'll use the console API directly if the process has
+     a console attached; otherwise we'll just use stdin and stderr. */
+  const HANDLE conin = CreateFileW(L"CONIN$", GENERIC_READ,
+                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
+                                   NULL, OPEN_EXISTING,
+                                   FILE_ATTRIBUTE_NORMAL, NULL);
+  if (conin != INVALID_HANDLE_VALUE)
+    {
+      /* The process has a console. */
+      CloseHandle(conin);
+      terminal->infd = terminal->outfd = NULL;
+      terminal->noecho = noecho;
+      terminal->close_handles = FALSE;
+      terminal->pool = NULL;
+      return SVN_NO_ERROR;
+    }
+#else  /* !WIN32 */
+  /* Without evidence to the contrary, we'll assume this is *nix and
+     try to open /dev/tty. If that fails, we'll use stdin for input
+     and stderr for prompting. */
+  apr_file_t *tmpf;
+  status = apr_file_open(&tmpf, "/dev/tty",
+                         APR_FOPEN_READ | APR_FOPEN_WRITE,
+                         APR_OS_DEFAULT, pool);
+  if (!status)
+    {
+      /* We have a terminal handle that we can use for input and output. */
+      terminal->infd = terminal->outfd = tmpf;
+      terminal->close_handles = TRUE;
+    }
+#endif /* !WIN32 */
+  else
+    {
+      /* There is no terminal. Sigh. */
+      status = apr_file_open_stdin(&terminal->infd, pool);
+      if (status)
+        return svn_error_wrap_apr(status, _("Can't open stdin"));
+      status = apr_file_open_stderr(&terminal->outfd, pool);
+      if (status)
+        return svn_error_wrap_apr(status, _("Can't open stderr"));
+      terminal->close_handles = FALSE;
+    }
+
+  /* Try to turn off terminal echo */
+  terminal->noecho = FALSE;
+#ifdef HAVE_TERMIOS_H
+  if (noecho && 0 == apr_os_file_get(&terminal->osinfd, terminal->infd))
+    {
+      if (0 == tcgetattr(terminal->osinfd, &terminal->attr))
+        {
+          struct termios attr = terminal->attr;
+          attr.c_lflag &= ~(ECHO);
+          if (0 == tcsetattr(terminal->osinfd, TCSAFLUSH, &attr))
+            terminal->noecho = TRUE;
+        }
+    }
+#endif /* HAVE_TERMIOS_H */
+
+  terminal->pool = pool;
+  apr_pool_cleanup_register(pool, terminal,
+                            terminal_plain_cleanup,
+                            terminal_child_cleanup);
+  return SVN_NO_ERROR;
 }
 
+/* Write a null-terminated STRING to TERMINAL.
+   Use POOL for allocations related to converting STRING from UTF-8. */
+static svn_error_t *
+terminal_puts(const char *string, terminal_handle_t *terminal,
+              apr_pool_t *pool)
+{
+  svn_error_t *err;
+  apr_status_t status;
+  const char *converted;
+
+  err = svn_cmdline_cstring_from_utf8(&converted, string, pool);
+  if (err)
+    {
+      svn_error_clear(err);
+      converted = svn_cmdline_cstring_from_utf8_fuzzy(string, pool);
+    }
+
+#ifdef WIN32
+  if (!terminal->outfd)
+    {
+      /* See terminal_open; we're using Console I/O. */
+      _cputs(converted);
+      return SVN_NO_ERROR;
+    }
+#endif
+
+  status = apr_file_write_full(terminal->outfd, converted,
+                               strlen(converted), NULL);
+  if (!status)
+    status = apr_file_flush(terminal->outfd);
+  if (status)
+    return svn_error_wrap_apr(status, _("Can't write to terminal"));
+  return SVN_NO_ERROR;
+}
+
+/* These codes can be returned from terminal_getc instead of a character. */
+#define TERMINAL_NONE  0x80000               /* no character read, retry */
+#define TERMINAL_DEL   (TERMINAL_NONE + 1)   /* the input was a deleteion */
+#define TERMINAL_EOL   (TERMINAL_NONE + 2)   /* end of input/end of line */
+#define TERMINAL_EOF   (TERMINAL_NONE + 3)   /* end of file during input */
+
+/* Read one character or control code from TERMINAL, returning it in CODE.
+   if CAN_ERASE and the input was a deletion, emit codes to erase the
+   last character displayed on the terminal.
+   Use POOL for all allocations. */
+static svn_error_t *
+terminal_getc(int *code, terminal_handle_t *terminal,
+              svn_boolean_t can_erase, apr_pool_t *pool)
+{
+  apr_status_t status;
+  char ch;
+
+#ifdef WIN32
+  if (!terminal->infd)
+    {
+      /* See terminal_open; we're using Console I/O. */
+      const svn_boolean_t echo = !terminal->noecho;
+
+      /*  The following was hoisted from APR's getpass for Windows. */
+      int concode = _getch();
+      switch (concode)
+        {
+        case '\r':                      /* end-of-line */
+          *code = TERMINAL_EOL;
+          if (echo)
+            _cputs("\r\n");
+          break;
+
+        case EOF:                       /* end-of-file */
+        case 26:                        /* Ctrl+Z */
+          *code = TERMINAL_EOF;
+          if (echo)
+            _cputs((concode == EOF ? "[EOF]\r\n" : "^Z\r\n"));
+          break;
+
+        case 3:                         /* Ctrl+C, Ctrl+Break */
+          /* _getch() bypasses Ctrl+C but not Ctrl+Break detection! */
+          if (echo)
+            _cputs("^C\r\n");
+          return svn_error_create(SVN_ERR_CANCELLED, NULL, NULL);
+
+        case 0:                         /* Function code prefix */
+        case 0xE0:
+          concode = (concode << 4) | _getch();
+          /* Catch {DELETE}, {<--}, Num{DEL} and Num{<--} */
+          if (concode == 0xE53 || concode == 0xE4B
+              || concode == 0x053 || concode == 0x04B)
+            {
+              *code = TERMINAL_DEL;
+              if (can_erase)
+                _cputs("\b \b");
+            }
+          else
+            {
+              *code = TERMINAL_NONE;
+              _putch('\a');
+            }
+          break;
+
+        case '\b':                      /* BS */
+        case 127:                       /* DEL */
+          *code = TERMINAL_DEL;
+          if (can_erase)
+            _cputs("\b \b");
+          break;
+
+        default:
+          if (!apr_iscntrl(concode))
+            {
+              *code = (int)(unsigned char)concode;
+              _putch(echo ? concode : '*');
+            }
+          else
+            {
+              *code = TERMINAL_NONE;
+              _putch('\a');
+            }
+        }
+      return SVN_NO_ERROR;
+    }
+#else  /* !WIN32 */
+  /* Wait for input on termin. This code is based on
+     apr_wait_for_io_or_timeout().
+     Note that this will return an EINTR on a signal. */
+  {
+    apr_pollfd_t pollset;
+    int n;
+
+    pollset.desc_type = APR_POLL_FILE;
+    pollset.desc.f = terminal->infd;
+    pollset.p = pool;
+    pollset.reqevents = APR_POLLIN;
+
+    status = apr_poll(&pollset, 1, &n, -1);
+
+    if (n == 1 && pollset.rtnevents & APR_POLLIN)
+      status = APR_SUCCESS;
+  }
+#endif /* !WIN32 */
+
+  /* Fall back to plain stream-based I/O. */
+  if (!status)
+    status = apr_file_getc(&ch, terminal->infd);
+  if (APR_STATUS_IS_EINTR(status))
+    {
+      *code = TERMINAL_NONE;
+      return SVN_NO_ERROR;
+    }
+  else if (APR_STATUS_IS_EOF(status))
+    {
+      *code = TERMINAL_EOF;
+      return SVN_NO_ERROR;
+    }
+  else if (status)
+    return svn_error_wrap_apr(status, _("Can't read from terminal"));
+
+  if (ch == '\b' || ch == 127 /* DEL */)
+    *code = TERMINAL_DEL;
+  else
+    *code = (int)(unsigned char)ch;
+  return SVN_NO_ERROR;
+}
+
+
 /* Set @a *result to the result of prompting the user with @a
  * prompt_msg.  Use @ *pb to get the cancel_func and cancel_baton.
  * Do not call the cancel_func if @a *pb is NULL.
@@ -91,77 +382,88 @@ prompt(const char **result,
   /* XXX: If this functions ever starts using members of *pb
    * which were not included in svn_cmdline_prompt_baton_t,
    * we need to update svn_cmdline_prompt_user2 and its callers. */
-  apr_status_t status;
-  apr_file_t *fp;
-  char c;
 
+  svn_boolean_t saw_first_half_of_eol = FALSE;
   svn_stringbuf_t *strbuf = svn_stringbuf_create_empty(pool);
+  terminal_handle_t terminal;
+  int code;
+  char c;
 
-  status = apr_file_open_stdin(&fp, pool);
-  if (status)
-    return svn_error_wrap_apr(status, _("Can't open stdin"));
+  SVN_ERR(terminal_open(&terminal, hide, pool));
+  SVN_ERR(terminal_puts(prompt_msg, &terminal, pool));
 
-  if (! hide)
+  while (1)
     {
-      svn_boolean_t saw_first_half_of_eol = FALSE;
-      SVN_ERR(svn_cmdline_fputs(prompt_msg, stderr, pool));
-      fflush(stderr);
+      /* Hack to allow us to not block for io on the prompt, so
+       * we can cancel. */
+      if (pb)
+        SVN_ERR(pb->cancel_func(pb->cancel_baton));
 
-      while (1)
+      SVN_ERR(terminal_getc(&code, &terminal, (strbuf->len > 0), pool));
+      switch (code)
         {
-          /* Hack to allow us to not block for io on the prompt, so
-           * we can cancel. */
-          if (pb)
-            SVN_ERR(pb->cancel_func(pb->cancel_baton));
-          status = wait_for_input(fp, pool);
-          if (APR_STATUS_IS_EINTR(status))
-            continue;
-          else if (status && status != APR_ENOTIMPL)
-            return svn_error_wrap_apr(status, _("Can't read stdin"));
-
-          status = apr_file_getc(&c, fp);
-          if (status)
-            return svn_error_wrap_apr(status, _("Can't read stdin"));
+        case TERMINAL_NONE:
+          /* Nothing useful happened; retry. */
+          continue;
+
+        case TERMINAL_DEL:
+          /* Delete the last input character. terminal_getc takes care
+             of erasing the feedback from the terminal, if applicable. */
+          svn_stringbuf_chop(strbuf, 1);
+          continue;
+
+        case TERMINAL_EOL:
+          /* End-of-line means end of input. Trick the EOL-detection code
+             below to stop reading. */
+          saw_first_half_of_eol = TRUE;
+          c = APR_EOL_STR[1];   /* Could be \0 but still stops reading. */
+          break;
+
+        case TERMINAL_EOF:
+          return svn_error_create(
+              APR_EOF,
+              terminal_close(&terminal),
+              _("End of file while reading from terminal"));
+
+        default:
+          /* Convert the returned code back to the character. */
+          c = (char)code;
+        }
 
-          if (saw_first_half_of_eol)
-            {
-              if (c == APR_EOL_STR[1])
-                break;
-              else
-                saw_first_half_of_eol = FALSE;
-            }
-          else if (c == APR_EOL_STR[0])
+      if (saw_first_half_of_eol)
+        {
+          if (c == APR_EOL_STR[1])
+            break;
+          else
+            saw_first_half_of_eol = FALSE;
+        }
+      else if (c == APR_EOL_STR[0])
+        {
+          /* GCC might complain here: "warning: will never be executed"
+           * That's fine. This is a compile-time check for "\r\n\0" */
+          if (sizeof(APR_EOL_STR) == 3)
             {
-              /* GCC might complain here: "warning: will never be executed"
-               * That's fine. This is a compile-time check for "\r\n\0" */
-              if (sizeof(APR_EOL_STR) == 3)
-                {
-                  saw_first_half_of_eol = TRUE;
-                  continue;
-                }
-              else if (sizeof(APR_EOL_STR) == 2)
-                break;
-              else
-                /* ### APR_EOL_STR holds more than two chars?  Who
-                   ever heard of such a thing? */
-                SVN_ERR_MALFUNCTION();
+              saw_first_half_of_eol = TRUE;
+              continue;
             }
-
-          svn_stringbuf_appendbyte(strbuf, c);
+          else if (sizeof(APR_EOL_STR) == 2)
+            break;
+          else
+            /* ### APR_EOL_STR holds more than two chars?  Who
+               ever heard of such a thing? */
+            SVN_ERR_MALFUNCTION();
         }
+
+      svn_stringbuf_appendbyte(strbuf, c);
     }
-  else
-    {
-      const char *prompt_stdout;
-      size_t bufsize = 300;
-      SVN_ERR(svn_cmdline_cstring_from_utf8(&prompt_stdout, prompt_msg,
-                                            pool));
-      svn_stringbuf_ensure(strbuf, bufsize);
 
-      status = apr_password_get(prompt_stdout, strbuf->data, &bufsize);
-      if (status)
-        return svn_error_wrap_apr(status, _("Can't get password"));
+  if (terminal.noecho)
+    {
+      /* If terminal echo was turned off, make sure future output
+         to the terminal starts on a new line, as expected. */
+      terminal_puts(APR_EOL_STR, &terminal, pool);
     }
+  SVN_ERR(terminal_close(&terminal));
 
   return svn_cmdline_cstring_to_utf8(result, strbuf->data, pool);
 }
@@ -179,9 +481,13 @@ maybe_print_realm(const char *realm, apr
 {
   if (realm)
     {
-      SVN_ERR(svn_cmdline_fprintf(stderr, pool,
-                                  _("Authentication realm: %s\n"), realm));
-      fflush(stderr);
+      terminal_handle_t terminal;
+      SVN_ERR(terminal_open(&terminal, FALSE, pool));
+      SVN_ERR(terminal_puts(
+                  apr_psprintf(pool,
+                               _("Authentication realm: %s\n"), realm),
+                  &terminal, pool));
+      SVN_ERR(terminal_close(&terminal));
     }
 
   return SVN_NO_ERROR;
@@ -396,13 +702,17 @@ plaintext_prompt_helper(svn_boolean_t *m
   svn_boolean_t answered = FALSE;
   svn_cmdline_prompt_baton2_t *pb = baton;
   const char *config_path = NULL;
+  terminal_handle_t terminal;
 
   if (pb)
     SVN_ERR(svn_config_get_user_config_path(&config_path, pb->config_dir,
                                             SVN_CONFIG_CATEGORY_SERVERS, pool));
 
-  SVN_ERR(svn_cmdline_fprintf(stderr, pool, prompt_text, realmstring,
-                              config_path));
+  SVN_ERR(terminal_open(&terminal, FALSE, pool));
+  SVN_ERR(terminal_puts(apr_psprintf(pool, prompt_text,
+                                     realmstring, config_path),
+                        &terminal, pool));
+  SVN_ERR(terminal_close(&terminal));
 
   do
     {



Re: svn commit: r1438998 - in /subversion/trunk: configure.ac subversion/libsvn_subr/prompt.c

Posted by Branko Čibej <br...@wandisco.com>.
On 27.01.2013 00:56, brane@apache.org wrote:
> Author: brane
> Date: Sat Jan 26 23:56:16 2013
> New Revision: 1438998
>
> URL: http://svn.apache.org/viewvc?rev=1438998&view=rev
> Log:
> Fix issue #4280: Prompt the controlling terminal, rather than stdin.

This change could potentially break the Windows tests. They pretty much
ran for me, at least those I tried, but ... running Windows tests in a
VM is painful. So, if anyone finds a bug that may be related to
prompting on Windows, please let me know and I'll try to repro and fix.

I saw the svn-slik-w2k3-x64-ra fail at about the time I committed, but
the build log is garbled and I couldn't make heads or tails of it.

-- Brane

-- 
Branko Čibej
Director of Subversion | WANdisco | www.wandisco.com