tests: Add run-threaded for multithread tests master
authorAndre Heinecke <aheinecke@intevation.de>
Thu, 15 Nov 2018 10:57:27 +0000 (11:57 +0100)
committerAndre Heinecke <aheinecke@intevation.de>
Thu, 15 Nov 2018 10:57:27 +0000 (11:57 +0100)
* tests/Makefile.am (run-threaded): Add.
* tests/run-threaded.c: New.

--
This test is intended to help detect race conditions
or other multithread problems. It can also be used
to put the whole GnuPG system under extreme load.

tests/Makefile.am
tests/run-threaded.c [new file with mode: 0644]

index 29e0c42..1b990e6 100644 (file)
@@ -34,8 +34,9 @@ noinst_HEADERS = run-support.h
 
 noinst_PROGRAMS = $(TESTS) run-keylist run-export run-import run-sign \
                  run-verify run-encrypt run-identify run-decrypt run-genkey \
 
 noinst_PROGRAMS = $(TESTS) run-keylist run-export run-import run-sign \
                  run-verify run-encrypt run-identify run-decrypt run-genkey \
-                 run-keysign run-tofu run-swdb
+                 run-keysign run-tofu run-swdb run-threaded
 
 
+run_threaded_LDADD = ../src/libgpgme.la -lpthread @GPG_ERROR_LIBS@
 
 if RUN_GPG_TESTS
 gpgtests = gpg json
 
 if RUN_GPG_TESTS
 gpgtests = gpg json
diff --git a/tests/run-threaded.c b/tests/run-threaded.c
new file mode 100644 (file)
index 0000000..14a9cbe
--- /dev/null
@@ -0,0 +1,678 @@
+/* run-threaded.c  - Helper to put GPGME under multithread load.
+   Copyright (C) 2018 by Bundesamt für Sicherheit in der Informationstechnik
+                 Software engineering by Intevation GmbH
+
+   This file is part of GPGME.
+
+   GPGME 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.1 of
+   the License, or (at your option) any later version.
+
+   GPGME 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 program; if not, see <https://www.gnu.org/licenses/>.
+*/
+
+/* The idea of this test is not to be run as unit test but as part
+ * of development to find threading issues and resource leaks. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gpgme.h>
+#include <gpg-error.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <unistd.h>
+
+#define PGM "run-threaded"
+
+#include "run-support.h"
+
+static volatile int stop;
+static volatile int thread_cnt;
+static volatile int running_threads;
+static int verbose;
+static int data_type;
+static int mem_only;
+
+#ifdef HAVE_W32_SYSTEM
+# include <windows.h>
+# define THREAD_RET DWORD CALLBACK
+
+static void
+create_thread (THREAD_RET (*func) (void *), void *arg)
+{
+  running_threads++;
+  if (CloseHandle (CreateThread (NULL, 0, func, arg, 0, NULL)))
+    {
+      fprintf (stderr, "Failed to create thread!\n");
+      exit (1);
+    }
+}
+
+#else
+# include <pthread.h>
+# define THREAD_RET void *
+
+static void
+create_thread (THREAD_RET (func) (void *), void *arg)
+{
+  pthread_t handle;
+  running_threads++;
+  if (pthread_create (&handle, NULL, func, arg))
+    {
+      fprintf (stderr, "Failed to create thread!\n");
+      exit (1);
+    }
+}
+#endif
+
+#include <gpg-error.h>
+
+GPGRT_LOCK_DEFINE (out_lock);
+
+#define out(format, ...) \
+  { \
+    gpgrt_lock_lock (&out_lock); \
+    printf (format "\n", ##__VA_ARGS__); \
+    gpgrt_lock_unlock (&out_lock); \
+  }
+
+#define errpoint \
+{ \
+  out ("Error on %i", __LINE__); \
+  exit (1); \
+}
+
+#define log(format, ...) \
+if (verbose) \
+  { \
+    gpgrt_lock_lock (&out_lock); \
+    printf (format "\n", ##__VA_ARGS__); \
+    gpgrt_lock_unlock (&out_lock); \
+  }
+
+/* Lazy mans signal */
+GPGRT_LOCK_DEFINE (threads_lock);
+
+void del_thread (void)
+{
+  if (--running_threads == 0)
+    {
+      gpgrt_lock_unlock (&threads_lock);
+    }
+}
+
+static int
+show_usage (int ex)
+{
+  fputs ("usage: " PGM " [options] [messages]\n\n"
+         "Options:\n"
+         "  --verbose     run in verbose mode\n"
+         "  --no-list     do not do keylistings\n"
+         "  --data-type   mem function to use one of:\n"
+         "                    1: fstream\n"
+         "                    2: posix fd\n"
+         "                    3: memory\n"
+         "                    4: gpgrt_stream\n"
+         "                    default: random\n"
+/*         "  --mem-cache   read data only once and then work on memory\n"
+           "                exlusive with data-type option\n" */
+         "  --threads N   use 4+N threads (4 are used for keylisting"
+" default 1)\n"
+         "  --repeat  N   do N repeats on the messages (default 1)\n\n"
+         "Note: The test does keylistings of both S/MIME and OpenPGP\n"
+         "      in the background while running operations on the\n"
+         "      messages, depending on their type.\n"
+         "      (Currently decrypt / verify).\n\n"
+         "      Without messages only keylistings will be done.\n"
+         , stderr);
+  exit (ex);
+}
+
+
+struct msg_list_s
+{
+  const char *file_name;
+  struct msg_list_s *next;
+};
+typedef struct msg_list_s *msg_list_t;
+
+struct data_s
+{
+  int fd;
+  FILE *file;
+  gpgrt_stream_t stream;
+  unsigned char *mem;
+  gpgme_data_t dh;
+};
+typedef struct data_s *data_t;
+
+struct keylist_args_s
+{
+  gpgme_protocol_t proto;
+  int secret;
+};
+typedef struct keylist_args_s *keylist_args_t;
+
+static volatile int keylists;
+
+static THREAD_RET
+do_keylist (void *keylist_args)
+{
+  gpgme_error_t err;
+  gpgme_ctx_t ctx;
+  gpgme_key_t key;
+
+  keylist_args_t args = (keylist_args_t) keylist_args;
+
+  log ("Keylist %i, Protocol: %s, Secret %i",
+       keylists++,
+       args->proto == GPGME_PROTOCOL_CMS ? "CMS" : "OpenPGP",
+       args->secret);
+
+  err = gpgme_new (&ctx);
+  fail_if_err (err);
+
+  err = gpgme_set_protocol (ctx, args->proto);
+  fail_if_err (err);
+
+  err = gpgme_op_keylist_start (ctx, NULL, args->secret);
+  fail_if_err (err);
+
+  while (!(err = gpgme_op_keylist_next (ctx, &key)))
+    {
+      gpgme_key_unref (key);
+    }
+
+  if (gpgme_err_code (err) != GPG_ERR_EOF)
+    {
+      fail_if_err (err);
+    }
+
+  gpgme_release (ctx);
+
+  if (!stop)
+    {
+      create_thread (do_keylist, keylist_args);
+    }
+  del_thread ();
+  return 0;
+}
+
+
+static unsigned char *
+get_file (const char *fname, size_t *r_size)
+{
+  gpg_error_t err;
+  gpgrt_stream_t fp;
+  struct stat st;
+  unsigned char *buf;
+  size_t buflen;
+
+  fp = gpgrt_fopen (fname, "r");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      fprintf (stderr, "Error: can't open '%s': %s\n", fname,
+               gpg_strerror (err));
+      return NULL;
+    }
+
+  if (fstat (gpgrt_fileno(fp), &st))
+    {
+      err = gpg_error_from_syserror ();
+      fprintf (stderr, "Error: can't stat '%s': %s\n", fname,
+               gpg_strerror (err));
+      gpgrt_fclose (fp);
+      return NULL;
+    }
+
+  buflen = st.st_size;
+  buf = malloc (buflen+1);
+  if (!buf)
+    {
+      fprintf (stderr, "Error: no mem\n");
+      gpgrt_fclose (fp);
+      return NULL;
+    }
+
+  if (gpgrt_fread (buf, buflen, 1, fp) != 1)
+    {
+      err = gpg_error_from_syserror ();
+      fprintf (stderr, "error reading '%s': %s\n", fname,
+               gpg_strerror (err));
+      gpgrt_fclose (fp);
+      free (buf);
+      return NULL;
+    }
+  buf[buflen] = 0;
+  gpgrt_fclose (fp);
+
+  if (r_size)
+    {
+      *r_size = buflen;
+    }
+
+  return buf;
+}
+
+/** Lets use random data. This should also introduce a bit
+    of randomness into the system by changing the runtimes
+    of various data functions. Esp. Mem against the file ops. */
+data_t
+random_data_new (const char *fname)
+{
+  data_t ret = calloc (1, sizeof (struct data_s));
+  int data_rand;
+  if (data_type)
+    {
+      data_rand = data_type;
+    }
+  else
+    {
+      data_rand = rand () % 3;
+    }
+
+  if (data_rand == 0) /* stream */
+    {
+      FILE *f_stream = fopen (fname, "rb");
+      if (!f_stream)
+        {
+          errpoint;
+        }
+      fail_if_err (gpgme_data_new_from_stream (&(ret->dh), f_stream));
+      ret->file = f_stream;
+      return ret;
+    }
+  if (data_rand == 1) /* fd */
+    {
+      int fd = open (fname, O_RDONLY);
+      gpgme_data_t dh;
+      if (fd == -1)
+        {
+          errpoint;
+        }
+      fail_if_err (gpgme_data_new_from_fd (&dh, fd));
+      ret->fd = fd;
+      ret->dh = dh;
+      return ret;
+    }
+  if (data_rand == 2) /* mem */
+    {
+      unsigned char *mem;
+      size_t size;
+
+      mem = get_file (fname, &size);
+      if (!mem)
+        {
+          errpoint;
+        }
+      fail_if_err (gpgme_data_new_from_mem (&(ret->dh),
+                                            (const char *)mem,
+                                            size, 0));
+      ret->mem = mem;
+      return ret;
+    }
+  if (data_rand == 3) /* estream */
+    {
+      gpgrt_stream_t stream = gpgrt_fopen (fname, "rb");
+
+      if (!stream)
+        {
+          errpoint;
+        }
+
+      fail_if_err (gpgme_data_new_from_estream (&(ret->dh), stream));
+      ret->stream = stream;
+      return ret;
+    }
+  /* notreached */
+  return ret;
+}
+
+void
+random_data_close (data_t data)
+{
+  if (data->dh)
+    {
+      gpgme_data_release (data->dh);
+    }
+  if (data->fd)
+    {
+      close (data->fd);
+    }
+  else if (data->file)
+    {
+      fclose (data->file);
+    }
+  else if (data->stream)
+    {
+      gpgrt_fclose (data->stream);
+    }
+  else if (data->mem)
+    {
+      free (data->mem);
+    }
+  free (data);
+}
+
+void
+verify (const char *fname, gpgme_protocol_t proto)
+{
+  gpgme_ctx_t ctx;
+  gpgme_error_t err;
+  gpgme_data_t out;
+  char *msg;
+  size_t msg_len;
+  data_t data = random_data_new (fname);
+
+  log ("Starting verify on: %s with protocol %s", fname,
+       proto == GPGME_PROTOCOL_CMS ? "CMS" : "OpenPGP");
+
+  gpgme_data_new (&out);
+
+  err = gpgme_new (&ctx);
+  fail_if_err (err);
+
+  err = gpgme_set_protocol (ctx, proto);
+  fail_if_err (err);
+
+  err = gpgme_op_verify (ctx, data->dh, NULL, out);
+  out ("Data: %p, %i %p %p %p", data->dh,
+       data->fd, data->file, data->stream,
+       data->mem);
+  fail_if_err (err);
+
+  msg = gpgme_data_release_and_get_mem (out, &msg_len);
+
+  if (msg_len)
+    {
+      log ("Verify result \n'%.*s'", (int)msg_len, msg);
+    }
+
+  gpgme_release (ctx);
+
+  random_data_close (data);
+}
+
+
+/* We randomize data access to put in a bit additional
+   entropy in this test and also to check if maybe
+   some data functions might not be properly thread
+   safe. */
+void
+decrypt (const char *fname, gpgme_protocol_t proto)
+{
+  gpgme_ctx_t ctx;
+  gpgme_error_t err;
+  gpgme_data_t out;
+  char *msg;
+  size_t msg_len;
+  data_t data = random_data_new (fname);
+
+  log ("Starting decrypt on: %s", fname);
+
+  gpgme_data_new (&out);
+
+  err = gpgme_new (&ctx);
+  fail_if_err (err);
+
+  err = gpgme_set_protocol (ctx, proto);
+  fail_if_err (err);
+
+  err = gpgme_op_decrypt (ctx, data->dh, out);
+  fail_if_err (err);
+
+  gpgme_release (ctx);
+
+  msg = gpgme_data_release_and_get_mem (out, &msg_len);
+
+  if (msg_len)
+    {
+      log ("Decrypt result \n'%.*s'", (int)msg_len, msg);
+    }
+
+  random_data_close (data);
+}
+
+
+static THREAD_RET
+do_data_op (void *file_name)
+{
+  gpgme_data_t data;
+  const char *fname = (const char *) file_name;
+  FILE *f = fopen (fname, "rb");
+  gpgme_data_type_t type;
+
+  if (!f)
+    {
+      fprintf (stderr, "Failed to open '%s'\n", fname);
+      exit (1);
+    }
+
+  fail_if_err (gpgme_data_new_from_stream (&data, f));
+
+  type = gpgme_data_identify (data, 0);
+  gpgme_data_release (data);
+  fclose (f);
+
+  switch (type)
+    {
+      case GPGME_DATA_TYPE_INVALID:
+      case GPGME_DATA_TYPE_UNKNOWN:
+        {
+          fprintf (stderr, "Failed to identify '%s'", fname);
+          exit(1);
+        }
+      case GPGME_DATA_TYPE_PGP_SIGNED:
+        {
+          verify (fname, GPGME_PROTOCOL_OpenPGP);
+          break;
+        }
+      case GPGME_DATA_TYPE_CMS_SIGNED:
+        {
+          verify (fname, GPGME_PROTOCOL_CMS);
+          break;
+        }
+      case GPGME_DATA_TYPE_PGP_ENCRYPTED:
+        {
+          decrypt (fname, GPGME_PROTOCOL_OpenPGP);
+          break;
+        }
+      case GPGME_DATA_TYPE_CMS_ENCRYPTED:
+        {
+          decrypt (fname, GPGME_PROTOCOL_CMS);
+          break;
+        }
+      default:
+        {
+          fprintf (stderr, "Unhandled data type 0x%x for '%s'", type, fname);
+          exit(1);
+        }
+    }
+
+  del_thread ();
+  return 0;
+}
+
+
+void
+start_keylistings (void)
+{
+  static struct keylist_args_s args[4];
+
+  args[0].proto = GPGME_PROTOCOL_OpenPGP;
+  args[0].secret = 0;
+
+  args[1].proto = GPGME_PROTOCOL_OpenPGP;
+  args[1].secret = 1;
+
+  args[2].proto = GPGME_PROTOCOL_CMS;
+  args[2].secret = 0;
+
+  args[3].proto = GPGME_PROTOCOL_CMS;
+  args[3].secret = 1;
+
+  for (int i = 0; i < 4; i++)
+    {
+      thread_cnt--;
+      create_thread (do_keylist, &args[i]);
+    }
+}
+
+int
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+  int repeats = 1;
+  int threads = 0;
+  int no_list = 0;
+  msg_list_t msgs = NULL;
+  msg_list_t msg_it = NULL;
+  stop = 0;
+
+  srand (1 /* Somewhat deterministic results */);
+
+  if (argc)
+    { argc--; argv++; }
+
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--help"))
+        {
+          show_usage (0);
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--no-list"))
+        {
+          no_list = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--mem-only"))
+        {
+          if (data_type)
+            {
+              show_usage (1);
+            }
+          mem_only = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--threads"))
+        {
+          argc--; argv++;
+          if (!argc)
+            {
+              show_usage (1);
+            }
+          threads = atoi (*argv);
+          if (!threads)
+            {
+              show_usage (1);
+            }
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--data-type"))
+        {
+          argc--; argv++;
+          if (!argc || mem_only)
+            {
+              show_usage (1);
+            }
+          data_type = atoi (*argv);
+          if (data_type > 4)
+            {
+              show_usage (1);
+            }
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--repeat"))
+        {
+          argc--; argv++;
+          if (!argc)
+            {
+              show_usage (1);
+            }
+          repeats = atoi (*argv);
+          if (!repeats)
+            {
+              show_usage (1);
+            }
+          argc--; argv++;
+        }
+    }
+
+  init_gpgme_basic ();
+
+  if (threads < argc)
+    {
+      /* Make sure we run once on each arg */
+      threads += argc;
+    }
+  while (argc)
+    {
+      if (!msgs)
+        {
+          msgs = calloc (1, sizeof *msgs);
+          msg_it = msgs;
+        }
+      else
+        {
+          msg_it->next = calloc (1, sizeof *msgs);
+          msg_it = msg_it->next;
+        }
+      msg_it->file_name = *argv;
+      argc--; argv++;
+    }
+
+  gpgrt_lock_lock (&threads_lock);
+  do
+    {
+      stop = 0;
+      thread_cnt = threads + 4;
+      out ("Repeats left: %i", repeats);
+
+      if (!no_list)
+        {
+          start_keylistings ();
+        }
+      else
+        {
+          thread_cnt -= 4;
+        }
+
+      while (thread_cnt)
+        {
+          log ("Thread %i", thread_cnt);
+          for (msg_it = msgs; msg_it && thread_cnt; msg_it = msg_it->next)
+            {
+              thread_cnt--;
+              create_thread (do_data_op, (void *)msg_it->file_name);
+            }
+        }
+
+      stop = 1;
+      gpgrt_lock_lock (&threads_lock);
+    }
+  while (--repeats != 0);
+
+  return 0;
+}