[Libguestfs] [PATCH 1/5] Import miniexpect library

Pino Toscano ptoscano at redhat.com
Tue Jul 9 14:05:14 UTC 2019


Add a copy of the miniexpect library that can be found here:
http://git.annexia.org/?p=miniexpect.git;a=summary
It is imported at commit eba90008396f05e2c0fa752421793b797383fca7.
---
 Makefile.am             |   1 +
 miniexpect/README       |  31 +++
 miniexpect/miniexpect.c | 489 ++++++++++++++++++++++++++++++++++++++++
 miniexpect/miniexpect.h | 110 +++++++++
 4 files changed, 631 insertions(+)
 create mode 100644 miniexpect/README
 create mode 100644 miniexpect/miniexpect.c
 create mode 100644 miniexpect/miniexpect.h

diff --git a/Makefile.am b/Makefile.am
index 4e2405e..736b8fc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -38,6 +38,7 @@ EXTRA_DIST = \
 	kiwi-config.sh \
 	kiwi-config.xml.in \
 	launch-virt-p2v \
+	miniexpect/README \
 	p2v.ks.in \
 	p2v.service \
 	test-virt-p2v-docs.sh \
diff --git a/miniexpect/README b/miniexpect/README
new file mode 100644
index 0000000..f1f5a95
--- /dev/null
+++ b/miniexpect/README
@@ -0,0 +1,31 @@
+miniexpect is a very simple expect-like library for C.
+
+It has a saner interface than libexpect, and doesn't depend on Tcl.
+It is also thread safe, const-correct and uses modern C standards.
+
+It is standalone, except that it requires the PCRE (Perl Compatible
+Regular Expressions) library from http://www.pcre.org/.  The PCRE
+dependency is fundamental because we want to offer the most powerful
+regular expression syntax to match on, but more importantly because
+PCRE has a convenient way to detect partial matches which made this
+library very simple to implement.
+
+License
+-------
+
+The library was written by Richard W.M. Jones <rjones at redhat.com>
+and is licensed under the Library GPL (LGPL) version 2 or above.
+
+Source is available from: http://git.annexia.org/?p=miniexpect.git;a=summary
+
+Using the library
+-----------------
+
+If you wanted to copy the library into your own code (instead of
+linking to it as a dependency), you only need to copy the two files:
+miniexpect.h, miniexpect.c.
+
+The API is documented in the manual page (miniexpect.pod / miniexpect.3).
+
+For examples of how to use the API in reality, see the examples and
+tests in the source directory.
diff --git a/miniexpect/miniexpect.c b/miniexpect/miniexpect.c
new file mode 100644
index 0000000..7debc02
--- /dev/null
+++ b/miniexpect/miniexpect.c
@@ -0,0 +1,489 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <poll.h>
+#include <errno.h>
+#include <termios.h>
+#include <time.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include <pcre.h>
+
+/* 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"
+
+static void debug_buffer (FILE *, const char *);
+
+static mexp_h *
+create_handle (void)
+{
+  mexp_h *h = malloc (sizeof *h);
+  if (h == NULL)
+    return NULL;
+
+  /* Initialize the fields to default values. */
+  h->fd = -1;
+  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;
+}
+
+static void
+clear_buffer (mexp_h *h)
+{
+  free (h->buffer);
+  h->buffer = NULL;
+  h->alloc = h->len = 0;
+  h->next_match = -1;
+}
+
+int
+mexp_close (mexp_h *h)
+{
+  int status = 0;
+
+  free (h->buffer);
+
+  if (h->fd >= 0)
+    close (h->fd);
+  if (h->pid > 0) {
+    if (waitpid (h->pid, &status, 0) == -1)
+      return -1;
+  }
+
+  free (h);
+
+  return status;
+}
+
+mexp_h *
+mexp_spawnlf (unsigned flags, const char *file, const char *arg, ...)
+{
+  char **argv, **new_argv;
+  size_t i;
+  va_list args;
+  mexp_h *h;
+
+  argv = malloc (sizeof (char *));
+  if (argv == NULL)
+    return NULL;
+  argv[0] = (char *) arg;
+
+  va_start (args, arg);
+  for (i = 1; arg != NULL; ++i) {
+    arg = va_arg (args, const char *);
+    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_spawnvf (flags, file, argv);
+  free (argv);
+  va_end (args);
+  return h;
+}
+
+mexp_h *
+mexp_spawnvf (unsigned flags, const char *file, char **argv)
+{
+  mexp_h *h = NULL;
+  int fd = -1;
+  int err;
+  char slave[1024];
+  pid_t pid = 0;
+
+  fd = posix_openpt (O_RDWR|O_NOCTTY);
+  if (fd == -1)
+    goto error;
+
+  if (grantpt (fd) == -1)
+    goto error;
+
+  if (unlockpt (fd) == -1)
+    goto error;
+
+  /* Get the slave pty name now, but don't open it in the parent. */
+  if (ptsname_r (fd, slave, sizeof slave) != 0)
+    goto error;
+
+  /* Create the handle last before we fork. */
+  h = create_handle ();
+  if (h == NULL)
+    goto error;
+
+  pid = fork ();
+  if (pid == -1)
+    goto error;
+
+  if (pid == 0) {               /* Child. */
+    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
+     * after setsid so it becomes our controlling tty.
+     */
+    slave_fd = open (slave, O_RDWR);
+    if (slave_fd == -1)
+      goto error;
+
+    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);
+    dup2 (slave_fd, 1);
+    dup2 (slave_fd, 2);
+    close (slave_fd);
+
+    /* Close the master side of the pty - do this late to avoid a
+     * kernel bug, see sshpass source code.
+     */
+    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);
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Parent. */
+
+  h->fd = fd;
+  h->pid = pid;
+  return h;
+
+ error:
+  err = errno;
+  if (fd >= 0)
+    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 mexp_regexp *regexps, int *ovector, int ovecsize)
+{
+  time_t start_t, now_t;
+  int timeout;
+  struct pollfd pfds[1];
+  int r;
+  ssize_t rs;
+
+  time (&start_t);
+
+  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.
+     * Timeout == 0 is not particularly well-defined, but it probably
+     * means "return immediately if there's no data to be read".
+     */
+    if (h->timeout >= 0) {
+      time (&now_t);
+      timeout = h->timeout - ((now_t - start_t) * 1000);
+      if (timeout < 0)
+        timeout = 0;
+    }
+    else
+      timeout = 0;
+
+    pfds[0].fd = h->fd;
+    pfds[0].events = POLLIN;
+    pfds[0].revents = 0;
+    r = poll (pfds, 1, timeout);
+    if (h->debug_fp)
+      fprintf (h->debug_fp, "DEBUG: poll returned %d\n", r);
+    if (r == -1)
+      return MEXP_ERROR;
+
+    if (r == 0)
+      return MEXP_TIMEOUT;
+
+    /* Otherwise we expect there is something to read from the file
+     * descriptor.
+     */
+    if (h->alloc - h->len <= h->read_size) {
+      char *new_buffer;
+      /* +1 here allows us to store \0 after the data read */
+      new_buffer = realloc (h->buffer, h->alloc + h->read_size + 1);
+      if (new_buffer == NULL)
+        return MEXP_ERROR;
+      h->buffer = new_buffer;
+      h->alloc += h->read_size;
+    }
+    rs = read (h->fd, h->buffer + h->len, h->read_size);
+    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,
+       * and read returns -1 / EIO.  Handle that special case here.
+       */
+      if (errno == EIO)
+        return MEXP_EOF;
+      return MEXP_ERROR;
+    }
+    if (rs == 0)
+      return MEXP_EOF;
+
+    /* We read something. */
+    h->len += rs;
+    h->buffer[h->len] = '\0';
+    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;
+
+      assert (h->buffer != NULL);
+
+      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;
+          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;
+        }
+      }
+
+      /* 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);
+
+    } /* if (regexps) */
+  }
+}
+
+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)
+{
+  char *msg;
+  int len;
+  size_t n;
+  ssize_t r;
+  char *p;
+
+  len = vasprintf (&msg, fs, args);
+
+  if (len < 0)
+    return -1;
+
+  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;
+  while (n > 0) {
+    r = write (h->fd, p, n);
+    if (r == -1) {
+      free (msg);
+      return -1;
+    }
+    n -= r;
+    p += r;
+  }
+
+  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++;
+  }
+}
diff --git a/miniexpect/miniexpect.h b/miniexpect/miniexpect.h
new file mode 100644
index 0000000..14d8236
--- /dev/null
+++ b/miniexpect/miniexpect.h
@@ -0,0 +1,110 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* ** NOTE ** All API documentation is in the manual page.
+ *
+ * To read the manual page from the source directory, do:
+ *    man ./miniexpect.3
+ * If you have installed miniexpect, do:
+ *    man 3 miniexpect
+ *
+ * The source for the manual page is miniexpect.pod.
+ */
+
+#ifndef MINIEXPECT_H_
+#define MINIEXPECT_H_
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <pcre.h>
+
+/* This handle is created per subprocess that is spawned. */
+struct mexp_h {
+  int fd;
+  pid_t pid;
+  int timeout;
+  char *buffer;
+  size_t len;
+  size_t alloc;
+  ssize_t next_match;
+  size_t read_size;
+  int pcre_error;
+  FILE *debug_fp;
+  void *user1;
+  void *user2;
+  void *user3;
+};
+typedef struct mexp_h mexp_h;
+
+/* Methods to access (some) fields in the handle. */
+#define mexp_get_fd(h) ((h)->fd)
+#define mexp_get_pid(h) ((h)->pid)
+#define mexp_get_timeout_ms(h) ((h)->timeout)
+#define mexp_set_timeout_ms(h, ms) ((h)->timeout = (ms))
+/* If secs == -1, then this sets h->timeout to -1000, but the main
+ * code handles this since it only checks for h->timeout < 0.
+ */
+#define mexp_set_timeout(h, secs) ((h)->timeout = 1000 * (secs))
+#define mexp_get_read_size(h) ((h)->read_size)
+#define mexp_set_read_size(h, size) ((h)->read_size = (size))
+#define mexp_get_pcre_error(h) ((h)->pcre_error)
+#define mexp_set_debug_file(h, fp) ((h)->debug_fp = (fp))
+#define mexp_get_debug_file(h) ((h)->debug_fp)
+
+/* Spawn a subprocess. */
+extern mexp_h *mexp_spawnvf (unsigned flags, const char *file, char **argv);
+extern mexp_h *mexp_spawnlf (unsigned flags, const char *file, const char *arg, ...);
+#define mexp_spawnv(file,argv) mexp_spawnvf (0, (file), (argv))
+#define mexp_spawnl(file,...) mexp_spawnlf (0, (file), __VA_ARGS__)
+
+#define MEXP_SPAWN_KEEP_SIGNALS 1
+#define MEXP_SPAWN_KEEP_FDS     2
+#define MEXP_SPAWN_COOKED_MODE  4
+#define MEXP_SPAWN_RAW_MODE     0
+
+/* Close the handle. */
+extern int mexp_close (mexp_h *h);
+
+/* Expect. */
+struct mexp_regexp {
+  int r;
+  const pcre *re;
+  const pcre_extra *extra;
+  int options;
+};
+typedef struct mexp_regexp mexp_regexp;
+
+enum mexp_status {
+  MEXP_EOF        = 0,
+  MEXP_ERROR      = -1,
+  MEXP_PCRE_ERROR = -2,
+  MEXP_TIMEOUT    = -3,
+};
+
+extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps,
+                        int *ovector, int ovecsize);
+
+/* Sending commands, keypresses. */
+extern int mexp_printf (mexp_h *h, const char *fs, ...)
+  __attribute__((format(printf,2,3)));
+extern int mexp_printf_password (mexp_h *h, const char *fs, ...)
+  __attribute__((format(printf,2,3)));
+extern int mexp_send_interrupt (mexp_h *h);
+
+#endif /* MINIEXPECT_H_ */
-- 
2.21.0




More information about the Libguestfs mailing list