tests: Add run-threaded for multithread tests
* 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.
This commit is contained in:
parent
004e2cad2f
commit
d0402f886b
@ -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 \
|
||||
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
|
||||
|
678
tests/run-threaded.c
Normal file
678
tests/run-threaded.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user