X-Git-Url: http://git.annexia.org/?a=blobdiff_plain;f=miniexpect.c;h=85826a0603e5260783ecf8647cc561403501a0da;hb=6a57e58c3c2bd2df7d22545f850c469f5fe7f58f;hp=f525b68630a7f15fa6b9d97abb7e9b2d634ff4af;hpb=5cac23de1963880d8fb3aed4be47ec2894cf520c;p=miniexpect.git diff --git a/miniexpect.c b/miniexpect.c index f525b68..85826a0 100644 --- a/miniexpect.c +++ b/miniexpect.c @@ -21,8 +21,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -34,9 +37,16 @@ #include +/* RHEL 6 pcre did not define PCRE_PARTIAL_SOFT. However PCRE_PARTIAL + * is a synonym so use that. + */ +#ifndef PCRE_PARTIAL_SOFT +#define PCRE_PARTIAL_SOFT PCRE_PARTIAL +#endif + #include "miniexpect.h" -#define DEBUG 0 +static void debug_buffer (FILE *, const char *); static mexp_h * create_handle (void) @@ -50,8 +60,11 @@ 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->next_match = -1; + h->debug_fp = NULL; h->user1 = h->user2 = h->user3 = NULL; return h; @@ -63,6 +76,7 @@ clear_buffer (mexp_h *h) free (h->buffer); h->buffer = NULL; h->alloc = h->len = 0; + h->next_match = -1; } int @@ -79,11 +93,13 @@ mexp_close (mexp_h *h) return -1; } + free (h); + return status; } mexp_h * -mexp_spawnl (const char *file, const char *arg, ...) +mexp_spawnlf (unsigned flags, const char *file, const char *arg, ...) { char **argv, **new_argv; size_t i; @@ -101,21 +117,23 @@ mexp_spawnl (const char *file, const char *arg, ...) new_argv = realloc (argv, sizeof (char *) * (i+1)); if (new_argv == NULL) { free (argv); + va_end (args); return NULL; } argv = new_argv; argv[i] = (char *) arg; } - h = mexp_spawnv (file, argv); + h = mexp_spawnvf (flags, file, argv); free (argv); + va_end (args); return h; } mexp_h * -mexp_spawnv (const char *file, char **argv) +mexp_spawnvf (unsigned flags, const char *file, char **argv) { - mexp_h *h; + mexp_h *h = NULL; int fd = -1; int err; char slave[1024]; @@ -145,9 +163,25 @@ mexp_spawnv (const char *file, char **argv) goto error; if (pid == 0) { /* Child. */ - struct termios terminal_settings; int slave_fd; + if (!(flags & MEXP_SPAWN_KEEP_SIGNALS)) { + struct sigaction sa; + int i; + + /* Remove all signal handlers. See the justification here: + * https://www.redhat.com/archives/libvir-list/2008-August/msg00303.html + * We don't mask signal handlers yet, so this isn't completely + * race-free, but better than not doing it at all. + */ + memset (&sa, 0, sizeof sa); + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset (&sa.sa_mask); + for (i = 1; i < NSIG; ++i) + sigaction (i, &sa, NULL); + } + setsid (); /* Open the slave side of the pty. We must do this in the child @@ -157,10 +191,14 @@ mexp_spawnv (const char *file, char **argv) if (slave_fd == -1) goto error; - /* Set raw mode. */ - tcgetattr (slave_fd, &terminal_settings); - cfmakeraw (&terminal_settings); - tcsetattr (slave_fd, TCSANOW, &terminal_settings); + if (!(flags & MEXP_SPAWN_COOKED_MODE)) { + struct termios termios; + + /* Set raw mode. */ + tcgetattr (slave_fd, &termios); + cfmakeraw (&termios); + tcsetattr (slave_fd, TCSANOW, &termios); + } /* Set up stdin, stdout, stderr to point to the pty. */ dup2 (slave_fd, 0); @@ -173,6 +211,21 @@ mexp_spawnv (const char *file, char **argv) */ close (fd); + if (!(flags & MEXP_SPAWN_KEEP_FDS)) { + int i, max_fd; + + /* Close all other file descriptors. This ensures that we don't + * hold open (eg) pipes from the parent process. + */ + max_fd = sysconf (_SC_OPEN_MAX); + if (max_fd == -1) + max_fd = 1024; + if (max_fd > 65536) + max_fd = 65536; /* bound the amount of work we do here */ + for (i = 3; i < max_fd; ++i) + close (i); + } + /* Run the subprocess. */ execvp (file, argv); perror (file); @@ -191,15 +244,14 @@ mexp_spawnv (const char *file, char **argv) close (fd); if (pid > 0) waitpid (pid, NULL, 0); + if (h != NULL) + mexp_close (h); errno = err; return NULL; } 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,10 +261,19 @@ mexp_expect (mexp_h *h, const pcre *code, time (&start_t); - options |= PCRE_PARTIAL_SOFT; - - /* Clear the read buffer. */ - clear_buffer (h); + if (h->next_match == -1) { + /* Fully clear the buffer, then read. */ + clear_buffer (h); + } else { + /* See the comment in the manual about h->next_match. We have + * some data remaining in the buffer, so begin by matching that. + */ + memmove (&h->buffer[0], &h->buffer[h->next_match], h->len - h->next_match); + h->len -= h->next_match; + h->buffer[h->len] = '\0'; + h->next_match = -1; + goto try_match; + } for (;;) { /* If we've got a timeout then work out how many seconds are left. @@ -232,9 +293,8 @@ mexp_expect (mexp_h *h, const pcre *code, pfds[0].events = POLLIN; pfds[0].revents = 0; r = poll (pfds, 1, timeout); -#if DEBUG - fprintf (stderr, "DEBUG: poll returned %d\n", r); -#endif + if (h->debug_fp) + fprintf (h->debug_fp, "DEBUG: poll returned %d\n", r); if (r == -1) return MEXP_ERROR; @@ -254,9 +314,8 @@ mexp_expect (mexp_h *h, const pcre *code, h->alloc += h->read_size; } rs = read (h->fd, h->buffer + h->len, h->read_size); -#if DEBUG - fprintf (stderr, "DEBUG: read returned %zd\n", rs); -#endif + if (h->debug_fp) + fprintf (h->debug_fp, "DEBUG: read returned %zd\n", rs); if (rs == -1) { /* Annoyingly on Linux (I'm fairly sure this is a bug) if the * writer closes the connection, the entire pty is destroyed, @@ -272,62 +331,94 @@ mexp_expect (mexp_h *h, const pcre *code, /* We read something. */ h->len += rs; h->buffer[h->len] = '\0'; -#if DEBUG - fprintf (stderr, "DEBUG: read %zd bytes from pty\n", rs); - fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer); -#endif + if (h->debug_fp) { + fprintf (h->debug_fp, "DEBUG: read %zd bytes from pty\n", rs); + fprintf (h->debug_fp, "DEBUG: buffer content: "); + debug_buffer (h->debug_fp, h->buffer); + fprintf (h->debug_fp, "\n"); + } + + try_match: + /* See if there is a full or partial match against any regexp. */ + if (regexps) { + size_t i; + int can_clear_buffer = 1; - /* See if there is a full or partial match against the regular expression. */ - if (code) { 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) { + const 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. */ + if (ovector != NULL && ovecsize >= 1 && ovector[1] >= 0) + h->next_match = ovector[1]; + else + h->next_match = -1; + if (h->debug_fp) + fprintf (h->debug_fp, "DEBUG: next_match at buffer offset %zu\n", + h->next_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) */ } } -int -mexp_printf (mexp_h *h, const char *fs, ...) +static int mexp_vprintf (mexp_h *h, int password, const char *fs, va_list args) + __attribute__((format(printf,3,0))); + +static int +mexp_vprintf (mexp_h *h, int password, const char *fs, va_list args) { - va_list args; char *msg; int len; size_t n; ssize_t r; char *p; - va_start (args, fs); len = vasprintf (&msg, fs, args); - va_end (args); if (len < 0) return -1; -#if DEBUG - fprintf (stderr, "DEBUG: writing: %s\n", msg); -#endif + if (h->debug_fp) { + if (!password) { + fprintf (h->debug_fp, "DEBUG: writing: "); + debug_buffer (h->debug_fp, msg); + fprintf (h->debug_fp, "\n"); + } + else + fprintf (h->debug_fp, "DEBUG: writing the password\n"); + } n = len; p = msg; @@ -344,3 +435,58 @@ mexp_printf (mexp_h *h, const char *fs, ...) free (msg); return len; } + +int +mexp_printf (mexp_h *h, const char *fs, ...) +{ + int r; + va_list args; + + va_start (args, fs); + r = mexp_vprintf (h, 0, fs, args); + va_end (args); + return r; +} + +int +mexp_printf_password (mexp_h *h, const char *fs, ...) +{ + int r; + va_list args; + + va_start (args, fs); + r = mexp_vprintf (h, 1, fs, args); + va_end (args); + return r; +} + +int +mexp_send_interrupt (mexp_h *h) +{ + return write (h->fd, "\003", 1); +} + +/* Print escaped buffer to fp. */ +static void +debug_buffer (FILE *fp, const char *buf) +{ + while (*buf) { + if (isprint (*buf)) + fputc (*buf, fp); + else { + switch (*buf) { + case '\0': fputs ("\\0", fp); break; + case '\a': fputs ("\\a", fp); break; + case '\b': fputs ("\\b", fp); break; + case '\f': fputs ("\\f", fp); break; + case '\n': fputs ("\\n", fp); break; + case '\r': fputs ("\\r", fp); break; + case '\t': fputs ("\\t", fp); break; + case '\v': fputs ("\\v", fp); break; + default: + fprintf (fp, "\\x%x", (unsigned char) *buf); + } + } + buf++; + } +}