mexp_expect: Change the expect API to allow multiple regexps.
authorRichard W.M. Jones <rjones@redhat.com>
Fri, 25 Apr 2014 21:19:03 +0000 (22:19 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Fri, 25 Apr 2014 21:31:36 +0000 (22:31 +0100)
example-sshpass.c
miniexpect.c
miniexpect.h

index a35c1d4..be9f8d0 100644 (file)
 
 #include "miniexpect.h"
 
-static pcre *
-compile_re (const char *rex)
-{
-  const char *errptr;
-  int erroffset;
-  pcre *ret;
-
-  ret = pcre_compile (rex, 0, &errptr, &erroffset, NULL);
-  if (ret == NULL) {
-    fprintf (stderr, "error: failed to compile regular expression '%s': %s at offset %d\n",
-             rex, errptr, erroffset);
-    exit (EXIT_FAILURE);
-  }
-  return ret;
-}
+static pcre *compile_re (const char *rex);
 
 int
 main (int argc, char *argv[])
@@ -65,7 +51,6 @@ main (int argc, char *argv[])
   pcre *password_re, *prompt_re, *hello_re;
   const int ovecsize = 12;
   int ovector[ovecsize];
-  int pcre_err;
 
   if (argc <= 3) {
     fprintf (stderr, "usage: sshpass PASSWORD ssh [SSH-ARGS...] HOST\n");
@@ -76,7 +61,7 @@ main (int argc, char *argv[])
 
   printf ("starting ssh command ...\n");
 
-  h = mexp_spawnv ("ssh", &argv[2]);
+  h = mexp_spawnv (argv[2], &argv[2]);
   if (h == NULL) {
     perror ("mexp_spawnv: ssh");
     exit (EXIT_FAILURE);
@@ -84,21 +69,26 @@ main (int argc, char *argv[])
 
   /* Wait for the password prompt. */
   password_re = compile_re ("assword");
-  switch (mexp_expect (h, password_re, NULL, 0, ovector, ovecsize, &pcre_err)) {
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = password_re },
+                         { 0 }
+                       },
+                       ovector, ovecsize)) {
+  case 100:
+    break;
   case MEXP_EOF:
     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
     goto error;
-  case MEXP_ERROR:
-    perror ("mexp_expect");
-    goto error;
   case MEXP_TIMEOUT:
     fprintf (stderr, "error: timeout before reaching the password prompt\n");
     goto error;
+  case MEXP_ERROR:
+    perror ("mexp_expect");
+    goto error;
   case MEXP_PCRE_ERROR:
-    fprintf (stderr, "error: PCRE error: %d\n", pcre_err);
+    fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error);
     goto error;
-  case MEXP_MATCHED:
-    break;
   }
 
   /* Got the password prompt, so send a password. */
@@ -115,31 +105,31 @@ main (int argc, char *argv[])
    * expect checks all these possibilities.  Unfortunately since all
    * prompts are a little bit different, we have to guess here.
    */
-  prompt_re = compile_re ("(assword)|([#$])");
-  switch (mexp_expect (h, prompt_re, NULL, 0, ovector, ovecsize, &pcre_err)) {
+  prompt_re = compile_re ("[#$]");
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = password_re },
+                         { 101, .re = prompt_re },
+                         { 0 },
+                       },
+                       ovector, ovecsize)) {
+  case 100:                     /* Password. */
+    fprintf (stderr, "error: ssh asked for password again, probably the password supplied is wrong\n");
+    goto error;
+  case 101:                     /* Prompt. */
+    break;
   case MEXP_EOF:
     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
     goto error;
-  case MEXP_ERROR:
-    perror ("mexp_expect");
-    goto error;
   case MEXP_TIMEOUT:
     fprintf (stderr, "error: timeout before reaching the prompt\n");
     goto error;
+  case MEXP_ERROR:
+    perror ("mexp_expect");
+    goto error;
   case MEXP_PCRE_ERROR:
-    fprintf (stderr, "error: PCRE error: %d\n", pcre_err);
-    goto error;
-  case MEXP_MATCHED:
-    /* Which part of the regexp matched? */
-    if (ovector[2] >= 0) {      /* password */
-      fprintf (stderr, "error: ssh asked for password again, probably the password supplied is wrong\n");
-      goto error;
-    }
-    else if (ovector[4] >= 0) { /* prompt */
-      break;
-    }
-    else
-      abort ();                 /* shouldn't happen */
+    fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error);
+    goto error;
   }
 
   /* Send a command which will have expected output. */
@@ -152,21 +142,26 @@ main (int argc, char *argv[])
 
   /* Wait for expected output from echo hello command. */
   hello_re = compile_re ("hello");
-  switch (mexp_expect (h, hello_re, NULL, 0, ovector, ovecsize, &pcre_err)) {
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = hello_re },
+                         { 0 },
+                       },
+                       ovector, ovecsize)) {
+  case 100:
+    break;
   case MEXP_EOF:
     fprintf (stderr, "error: ssh closed the connection unexpectedly\n");
     goto error;
-  case MEXP_ERROR:
-    perror ("mexp_expect");
-    goto error;
   case MEXP_TIMEOUT:
     fprintf (stderr, "error: timeout before reading command output\n");
     goto error;
+  case MEXP_ERROR:
+    perror ("mexp_expect");
+    goto error;
   case MEXP_PCRE_ERROR:
-    fprintf (stderr, "error: PCRE error: %d\n", pcre_err);
+    fprintf (stderr, "error: PCRE error: %d\n", h->pcre_error);
     goto error;
-  case MEXP_MATCHED:
-    break;
   }
 
   /* Send exit command and wait for ssh to exit. */
@@ -177,18 +172,17 @@ main (int argc, char *argv[])
     goto error;
   }
 
-  switch (mexp_expect (h, NULL, NULL, 0, NULL, 0, NULL)) {
+  switch (mexp_expect (h, NULL, NULL, 0)) {
   case MEXP_EOF:
     /* This is what we're expecting: ssh will close the connection. */
     break;
-  case MEXP_ERROR:
-    perror ("mexp_expect");
-    goto error;
   case MEXP_TIMEOUT:
     fprintf (stderr, "error: timeout before ssh closed the connection\n");
     goto error;
+  case MEXP_ERROR:
+    perror ("mexp_expect");
+    goto error;
   case MEXP_PCRE_ERROR:
-  case MEXP_MATCHED:
     fprintf (stderr, "error: unexpected return value from mexp_expect\n");
     goto error;
   }
@@ -209,3 +203,20 @@ main (int argc, char *argv[])
   mexp_close (h);
   exit (EXIT_FAILURE);
 }
+
+/* Helper function to compile a PCRE regexp. */
+static pcre *
+compile_re (const char *rex)
+{
+  const char *errptr;
+  int erroffset;
+  pcre *ret;
+
+  ret = pcre_compile (rex, 0, &errptr, &erroffset, NULL);
+  if (ret == NULL) {
+    fprintf (stderr, "error: failed to compile regular expression '%s': %s at offset %d\n",
+             rex, errptr, erroffset);
+    exit (EXIT_FAILURE);
+  }
+  return ret;
+}
index f525b68..fef03fd 100644 (file)
@@ -50,6 +50,7 @@ create_handle (void)
   h->pid = 0;
   h->timeout = 60000;
   h->read_size = 1024;
+  h->pcre_error = 0;
   h->buffer = NULL;
   h->len = h->alloc = 0;
   h->user1 = h->user2 = h->user3 = NULL;
@@ -196,10 +197,7 @@ mexp_spawnv (const char *file, char **argv)
 }
 
 enum mexp_status
-mexp_expect (mexp_h *h, const pcre *code,
-             const pcre_extra *extra,
-             int options, int *ovector, int ovecsize,
-             int *pcre_ret)
+mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
 {
   time_t start_t, now_t;
   int timeout;
@@ -209,8 +207,6 @@ mexp_expect (mexp_h *h, const pcre *code,
 
   time (&start_t);
 
-  options |= PCRE_PARTIAL_SOFT;
-
   /* Clear the read buffer. */
   clear_buffer (h);
 
@@ -277,34 +273,50 @@ mexp_expect (mexp_h *h, const pcre *code,
     fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer);
 #endif
 
-    /* See if there is a full or partial match against the regular expression. */
-    if (code) {
+    /* See if there is a full or partial match against any regexp. */
+    if (regexps) {
+      size_t i;
+      int can_clear_buffer = 1;
+
       assert (h->buffer != NULL);
-      r = pcre_exec (code, extra, h->buffer, (int)h->len, 0,
-                     options, ovector, ovecsize);
-      if (pcre_ret)
-        *pcre_ret = r;
-
-      if (r >= 0) {
-        /* A full match. */
-        return MEXP_MATCHED;
-      }
 
-      else if (r == PCRE_ERROR_NOMATCH) {
-        /* No match at all, so we can dump the input buffer. */
-        clear_buffer (h);
+      for (i = 0; regexps[i].r > 0; ++i) {
+        int options = regexps[i].options | PCRE_PARTIAL_SOFT;
+
+        r = pcre_exec (regexps[i].re, regexps[i].extra,
+                       h->buffer, (int)h->len, 0,
+                       options,
+                       ovector, ovecsize);
+        h->pcre_error = r;
+
+        if (r >= 0) {
+          /* A full match. */
+          return regexps[i].r;
+        }
+
+        else if (r == PCRE_ERROR_NOMATCH) {
+          /* No match at all. */
+          /* (nothing here) */
+        }
+
+        else if (r == PCRE_ERROR_PARTIAL) {
+          /* Partial match.  Keep the buffer and keep reading. */
+          can_clear_buffer = 0;
+        }
+
+        else {
+          /* An actual PCRE error. */
+          return MEXP_PCRE_ERROR;
+        }
       }
 
-      else if (r == PCRE_ERROR_PARTIAL) {
-        /* Partial match.  Keep the buffer and keep reading. */
-        /* (nothing here) */
-      }
+      /* If none of the regular expressions matched (not partially)
+       * then we can clear the buffer.  This is an optimization.
+       */
+      if (can_clear_buffer)
+        clear_buffer (h);
 
-      else {
-        /* An actual PCRE error. */
-        return MEXP_PCRE_ERROR;
-      }
-    } /* if (code) */
+    } /* if (regexps) */
   }
 }
 
index 8392ca5..9a2fa3c 100644 (file)
@@ -50,6 +50,11 @@ struct mexp_h {
    */
   size_t read_size;
 
+  /* If mexp_expect returns MEXP_PCRE_ERROR, then the actual PCRE
+   * error code is returned here.  See pcre_exec(3) for details.
+   */
+  int pcre_error;
+
   /* Opaque pointers for use of the caller.  The library will not
    * touch these.
    */
@@ -92,35 +97,30 @@ extern mexp_h *mexp_spawnl (const char *file, const char *arg, ...);
  */
 extern int mexp_close (mexp_h *h);
 
+/* The list of regular expressions passed to mexp_expect. */
+struct mexp_regexp {
+  int r;                        /* The returned value from mexp_expect.
+                                 * Must be > 0.
+                                 */
+  const pcre *re;               /* The compiled regular expression. */
+  const pcre_extra *extra;      /* See pcre_exec. */
+  int options;                  /* See pcre_exec. */
+};
+typedef struct mexp_regexp mexp_regexp;
+
 enum mexp_status {
-  MEXP_EOF = 0,
-  MEXP_ERROR = 1,
-  MEXP_TIMEOUT = 2,
-  MEXP_MATCHED = 3,
-  MEXP_PCRE_ERROR = 4,
+  MEXP_EOF        = -1,
+  MEXP_TIMEOUT    = -2,
+  MEXP_ERROR      = -3,
+  MEXP_PCRE_ERROR = -4,
 };
 
 /* Expect some output from the subprocess.  Match the output against
- * the PCRE regular expression.
- *
- * 'code', 'extra', 'options', 'ovector' and 'ovecsize' are passed
- * through to the pcre_exec function.  See pcreapi(3).
- *
- * If you want to match multiple strings, you have to combine them
- * into a single regexp, eg. "([Pp]assword)|([Ll]ogin)|([Ff]ailed)".
- * Then examine ovector[2], ovector[4], ovector[6] to see if they
- * contain '>= 0' or '-1'.  See the pcreapi(3) man page for further
- * information.
- *
- * 'code' may be NULL, which means we don't match against a regular
- * expression.  This is useful if you just want to wait for EOF or
- * timeout.
+ * the PCRE regular expression(s) in the list, and return which one
+ * matched.
  *
  * This can return:
  *
- *   MEXP_MATCHED:
- *     The input matched the regular expression.  Use ovector
- *     to find out what matched in the buffer (mexp_h->buffer).
  *   MEXP_TIMEOUT:
  *     No input matched before the timeout (mexp_h->timeout) was reached.
  *   MEXP_EOF:
@@ -128,13 +128,20 @@ enum mexp_status {
  *   MEXP_ERROR:
  *     There was a system call error (eg. from the read call).  See errno.
  *   MEXP_PCRE_ERROR
- *     There was a pcre_exec error.  *pcre_ret is set to the error code
+ *     There was a pcre_exec error.  h->pcre_error is set to the error code
  *     (see pcreapi(3) for a list of PCRE_* error codes and what they mean).
+ *
+ * Notes:
+ *
+ * - 'regexps' may be NULL or an empty list, which means we don't
+ *   match against a regular expression.  This is useful if you just
+ *   want to wait for EOF or timeout.
+ *
+ * - 'regexps[].re', 'regexps[].extra', 'regexps[].options', 'ovector'
+ *   and 'ovecsize' are passed through to the pcre_exec function.
  */
-extern enum mexp_status mexp_expect (mexp_h *h, const pcre *code,
-                                     const pcre_extra *extra,
-                                     int options, int *ovector, int ovecsize,
-                                     int *pcre_ret);
+extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps,
+                        int *ovector, int ovecsize);
 
 /* This is a convenience function for writing something (eg. a
  * password or command) to the subprocess.  You could do this by