From 16d4443d47b7a6943ae25cdfad57a5645d7a063c Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 25 Apr 2014 22:19:03 +0100 Subject: [PATCH] mexp_expect: Change the expect API to allow multiple regexps. --- example-sshpass.c | 119 +++++++++++++++++++++++++++++------------------------- miniexpect.c | 70 +++++++++++++++++++------------- miniexpect.h | 61 +++++++++++++++------------- 3 files changed, 140 insertions(+), 110 deletions(-) diff --git a/example-sshpass.c b/example-sshpass.c index a35c1d4..be9f8d0 100644 --- a/example-sshpass.c +++ b/example-sshpass.c @@ -40,21 +40,7 @@ #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; +} diff --git a/miniexpect.c b/miniexpect.c index f525b68..fef03fd 100644 --- a/miniexpect.c +++ b/miniexpect.c @@ -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) */ } } diff --git a/miniexpect.h b/miniexpect.h index 8392ca5..9a2fa3c 100644 --- a/miniexpect.h +++ b/miniexpect.h @@ -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 -- 1.8.3.1