core: Fix duplication of close_notify_handler for gpgsm.
[gpgme.git] / tests / run-threaded.c
1 /* run-threaded.c  - Helper to put GPGME under multithread load.
2  * Copyright (C) 2018 by Bundesamt für Sicherheit in der Informationstechnik
3  *               Software engineering by Intevation GmbH
4  *
5  * This file is part of GPGME.
6  *
7  * GPGME is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * GPGME is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, see <https://gnu.org/licenses/>.
19  * SPDX-License-Identifier: LGPL-2.1-or-later
20  */
21
22 /* The idea of this test is not to be run as unit test but as part
23  * of development to find threading issues and resource leaks. */
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28
29 #include <gpgme.h>
30 #include <gpg-error.h>
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <fcntl.h>
38
39 #include <unistd.h>
40
41 #define PGM "run-threaded"
42
43 #include "run-support.h"
44
45 static volatile int stop;
46 static volatile int thread_cnt;
47 static volatile int running_threads;
48 static int verbose;
49 static int data_type;
50 static int mem_only;
51
52 #ifdef HAVE_W32_SYSTEM
53 # include <windows.h>
54 # define THREAD_RET DWORD CALLBACK
55
56 static void
57 create_thread (THREAD_RET (*func) (void *), void *arg)
58 {
59   running_threads++;
60   if (CloseHandle (CreateThread (NULL, 0, func, arg, 0, NULL)))
61     {
62       fprintf (stderr, "Failed to create thread!\n");
63       exit (1);
64     }
65 }
66
67 #else
68 # include <pthread.h>
69 # define THREAD_RET void *
70
71 static void
72 create_thread (THREAD_RET (func) (void *), void *arg)
73 {
74   pthread_t handle;
75   running_threads++;
76   if (pthread_create (&handle, NULL, func, arg))
77     {
78       fprintf (stderr, "Failed to create thread!\n");
79       exit (1);
80     }
81 }
82 #endif
83
84 #include <gpg-error.h>
85
86 GPGRT_LOCK_DEFINE (out_lock);
87
88 #define out(format, ...) \
89   { \
90     gpgrt_lock_lock (&out_lock); \
91     printf (format "\n", ##__VA_ARGS__); \
92     gpgrt_lock_unlock (&out_lock); \
93   }
94
95 #define errpoint \
96 { \
97   out ("Error on %i", __LINE__); \
98   exit (1); \
99 }
100
101 #define log(format, ...) \
102 if (verbose) \
103   { \
104     gpgrt_lock_lock (&out_lock); \
105     printf (format "\n", ##__VA_ARGS__); \
106     gpgrt_lock_unlock (&out_lock); \
107   }
108
109 /* Lazy mans signal */
110 GPGRT_LOCK_DEFINE (threads_lock);
111
112 void del_thread (void)
113 {
114   if (--running_threads == 0)
115     {
116       gpgrt_lock_unlock (&threads_lock);
117     }
118 }
119
120 static int
121 show_usage (int ex)
122 {
123   fputs ("usage: " PGM " [options] [messages]\n\n"
124          "Options:\n"
125          "  --verbose     run in verbose mode\n"
126          "  --no-list     do not do keylistings\n"
127          "  --data-type   mem function to use one of:\n"
128          "                    1: fstream\n"
129          "                    2: posix fd\n"
130          "                    3: memory\n"
131          "                    4: gpgrt_stream\n"
132          "                    default: random\n"
133 /*         "  --mem-cache   read data only once and then work on memory\n"
134            "                exlusive with data-type option\n" */
135          "  --threads N   use 4+N threads (4 are used for keylisting"
136 " default 1)\n"
137          "  --repeat  N   do N repeats on the messages (default 1)\n\n"
138          "Note: The test does keylistings of both S/MIME and OpenPGP\n"
139          "      in the background while running operations on the\n"
140          "      messages, depending on their type.\n"
141          "      (Currently decrypt / verify).\n\n"
142          "      Without messages only keylistings will be done.\n"
143          , stderr);
144   exit (ex);
145 }
146
147
148 struct msg_list_s
149 {
150   const char *file_name;
151   struct msg_list_s *next;
152 };
153 typedef struct msg_list_s *msg_list_t;
154
155 struct data_s
156 {
157   int fd;
158   FILE *file;
159   gpgrt_stream_t stream;
160   unsigned char *mem;
161   gpgme_data_t dh;
162 };
163 typedef struct data_s *data_t;
164
165 struct keylist_args_s
166 {
167   gpgme_protocol_t proto;
168   int secret;
169 };
170 typedef struct keylist_args_s *keylist_args_t;
171
172 static volatile int keylists;
173
174 static THREAD_RET
175 do_keylist (void *keylist_args)
176 {
177   gpgme_error_t err;
178   gpgme_ctx_t ctx;
179   gpgme_key_t key;
180
181   keylist_args_t args = (keylist_args_t) keylist_args;
182
183   log ("Keylist %i, Protocol: %s, Secret %i",
184        keylists++,
185        args->proto == GPGME_PROTOCOL_CMS ? "CMS" : "OpenPGP",
186        args->secret);
187
188   err = gpgme_new (&ctx);
189   fail_if_err (err);
190
191   err = gpgme_set_protocol (ctx, args->proto);
192   fail_if_err (err);
193
194   err = gpgme_op_keylist_start (ctx, NULL, args->secret);
195   fail_if_err (err);
196
197   while (!(err = gpgme_op_keylist_next (ctx, &key)))
198     {
199       gpgme_key_unref (key);
200     }
201
202   if (gpgme_err_code (err) != GPG_ERR_EOF)
203     {
204       fail_if_err (err);
205     }
206
207   gpgme_release (ctx);
208
209   if (!stop)
210     {
211       create_thread (do_keylist, keylist_args);
212     }
213   del_thread ();
214   return 0;
215 }
216
217
218 static unsigned char *
219 get_file (const char *fname, size_t *r_size)
220 {
221   gpg_error_t err;
222   gpgrt_stream_t fp;
223   struct stat st;
224   unsigned char *buf;
225   size_t buflen;
226
227   fp = gpgrt_fopen (fname, "r");
228   if (!fp)
229     {
230       err = gpg_error_from_syserror ();
231       fprintf (stderr, "Error: can't open '%s': %s\n", fname,
232                gpg_strerror (err));
233       return NULL;
234     }
235
236   if (fstat (gpgrt_fileno(fp), &st))
237     {
238       err = gpg_error_from_syserror ();
239       fprintf (stderr, "Error: can't stat '%s': %s\n", fname,
240                gpg_strerror (err));
241       gpgrt_fclose (fp);
242       return NULL;
243     }
244
245   buflen = st.st_size;
246   buf = malloc (buflen+1);
247   if (!buf)
248     {
249       fprintf (stderr, "Error: no mem\n");
250       gpgrt_fclose (fp);
251       return NULL;
252     }
253
254   if (gpgrt_fread (buf, buflen, 1, fp) != 1)
255     {
256       err = gpg_error_from_syserror ();
257       fprintf (stderr, "error reading '%s': %s\n", fname,
258                gpg_strerror (err));
259       gpgrt_fclose (fp);
260       free (buf);
261       return NULL;
262     }
263   buf[buflen] = 0;
264   gpgrt_fclose (fp);
265
266   if (r_size)
267     {
268       *r_size = buflen;
269     }
270
271   return buf;
272 }
273
274 /** Lets use random data. This should also introduce a bit
275     of randomness into the system by changing the runtimes
276     of various data functions. Esp. Mem against the file ops. */
277 data_t
278 random_data_new (const char *fname)
279 {
280   data_t ret = calloc (1, sizeof (struct data_s));
281   int data_rand;
282   if (data_type)
283     {
284       data_rand = data_type;
285     }
286   else
287     {
288       data_rand = rand () % 3;
289     }
290
291   if (data_rand == 0) /* stream */
292     {
293       FILE *f_stream = fopen (fname, "rb");
294       if (!f_stream)
295         {
296           errpoint;
297         }
298       fail_if_err (gpgme_data_new_from_stream (&(ret->dh), f_stream));
299       ret->file = f_stream;
300       return ret;
301     }
302   if (data_rand == 1) /* fd */
303     {
304       int fd = open (fname, O_RDONLY);
305       gpgme_data_t dh;
306       if (fd == -1)
307         {
308           errpoint;
309         }
310       fail_if_err (gpgme_data_new_from_fd (&dh, fd));
311       ret->fd = fd;
312       ret->dh = dh;
313       return ret;
314     }
315   if (data_rand == 2) /* mem */
316     {
317       unsigned char *mem;
318       size_t size;
319
320       mem = get_file (fname, &size);
321       if (!mem)
322         {
323           errpoint;
324         }
325       fail_if_err (gpgme_data_new_from_mem (&(ret->dh),
326                                             (const char *)mem,
327                                             size, 0));
328       ret->mem = mem;
329       return ret;
330     }
331   if (data_rand == 3) /* estream */
332     {
333       gpgrt_stream_t stream = gpgrt_fopen (fname, "rb");
334
335       if (!stream)
336         {
337           errpoint;
338         }
339
340       fail_if_err (gpgme_data_new_from_estream (&(ret->dh), stream));
341       ret->stream = stream;
342       return ret;
343     }
344   /* notreached */
345   return ret;
346 }
347
348 void
349 random_data_close (data_t data)
350 {
351   if (data->dh)
352     {
353       gpgme_data_release (data->dh);
354     }
355   if (data->fd)
356     {
357       close (data->fd);
358     }
359   else if (data->file)
360     {
361       fclose (data->file);
362     }
363   else if (data->stream)
364     {
365       gpgrt_fclose (data->stream);
366     }
367   else if (data->mem)
368     {
369       free (data->mem);
370     }
371   free (data);
372 }
373
374 void
375 verify (const char *fname, gpgme_protocol_t proto)
376 {
377   gpgme_ctx_t ctx;
378   gpgme_error_t err;
379   gpgme_data_t out;
380   char *msg;
381   size_t msg_len;
382   data_t data = random_data_new (fname);
383
384   log ("Starting verify on: %s with protocol %s", fname,
385        proto == GPGME_PROTOCOL_CMS ? "CMS" : "OpenPGP");
386
387   gpgme_data_new (&out);
388
389   err = gpgme_new (&ctx);
390   fail_if_err (err);
391
392   err = gpgme_set_protocol (ctx, proto);
393   fail_if_err (err);
394
395   err = gpgme_op_verify (ctx, data->dh, NULL, out);
396   out ("Data: %p, %i %p %p %p", data->dh,
397        data->fd, data->file, data->stream,
398        data->mem);
399   fail_if_err (err);
400
401   msg = gpgme_data_release_and_get_mem (out, &msg_len);
402
403   if (msg_len)
404     {
405       log ("Verify result \n'%.*s'", (int)msg_len, msg);
406     }
407
408   gpgme_release (ctx);
409
410   random_data_close (data);
411   free (msg);
412 }
413
414
415 /* We randomize data access to put in a bit additional
416    entropy in this test and also to check if maybe
417    some data functions might not be properly thread
418    safe. */
419 void
420 decrypt (const char *fname, gpgme_protocol_t proto)
421 {
422   gpgme_ctx_t ctx;
423   gpgme_error_t err;
424   gpgme_data_t out;
425   char *msg;
426   size_t msg_len;
427   data_t data = random_data_new (fname);
428
429   log ("Starting decrypt on: %s", fname);
430
431   gpgme_data_new (&out);
432
433   err = gpgme_new (&ctx);
434   fail_if_err (err);
435
436   err = gpgme_set_protocol (ctx, proto);
437   fail_if_err (err);
438
439   err = gpgme_op_decrypt (ctx, data->dh, out);
440   fail_if_err (err);
441
442   gpgme_release (ctx);
443
444   msg = gpgme_data_release_and_get_mem (out, &msg_len);
445
446   if (msg_len)
447     {
448       log ("Decrypt result \n'%.*s'", (int)msg_len, msg);
449     }
450
451   random_data_close (data);
452 }
453
454
455 static THREAD_RET
456 do_data_op (void *file_name)
457 {
458   gpgme_data_t data;
459   const char *fname = (const char *) file_name;
460   FILE *f = fopen (fname, "rb");
461   gpgme_data_type_t type;
462
463   if (!f)
464     {
465       fprintf (stderr, "Failed to open '%s'\n", fname);
466       exit (1);
467     }
468
469   fail_if_err (gpgme_data_new_from_stream (&data, f));
470
471   type = gpgme_data_identify (data, 0);
472   gpgme_data_release (data);
473   fclose (f);
474
475   switch (type)
476     {
477       case GPGME_DATA_TYPE_INVALID:
478       case GPGME_DATA_TYPE_UNKNOWN:
479         {
480           fprintf (stderr, "Failed to identify '%s'", fname);
481           exit(1);
482         }
483       case GPGME_DATA_TYPE_PGP_SIGNED:
484         {
485           verify (fname, GPGME_PROTOCOL_OpenPGP);
486           break;
487         }
488       case GPGME_DATA_TYPE_CMS_SIGNED:
489         {
490           verify (fname, GPGME_PROTOCOL_CMS);
491           break;
492         }
493       case GPGME_DATA_TYPE_PGP_ENCRYPTED:
494         {
495           decrypt (fname, GPGME_PROTOCOL_OpenPGP);
496           break;
497         }
498       case GPGME_DATA_TYPE_CMS_ENCRYPTED:
499         {
500           decrypt (fname, GPGME_PROTOCOL_CMS);
501           break;
502         }
503       default:
504         {
505           fprintf (stderr, "Unhandled data type 0x%x for '%s'", type, fname);
506           exit(1);
507         }
508     }
509
510   del_thread ();
511   return 0;
512 }
513
514
515 void
516 start_keylistings (void)
517 {
518   static struct keylist_args_s args[4];
519
520   args[0].proto = GPGME_PROTOCOL_OpenPGP;
521   args[0].secret = 0;
522
523   args[1].proto = GPGME_PROTOCOL_OpenPGP;
524   args[1].secret = 1;
525
526   args[2].proto = GPGME_PROTOCOL_CMS;
527   args[2].secret = 0;
528
529   args[3].proto = GPGME_PROTOCOL_CMS;
530   args[3].secret = 1;
531
532   for (int i = 0; i < 4; i++)
533     {
534       thread_cnt--;
535       create_thread (do_keylist, &args[i]);
536     }
537 }
538
539 int
540 main (int argc, char **argv)
541 {
542   int last_argc = -1;
543   int repeats = 1;
544   int threads = 0;
545   int no_list = 0;
546   msg_list_t msgs = NULL;
547   msg_list_t msg_it = NULL;
548   stop = 0;
549
550   srand (1 /* Somewhat deterministic results */);
551
552   if (argc)
553     { argc--; argv++; }
554
555   while (argc && last_argc != argc )
556     {
557       last_argc = argc;
558       if (!strcmp (*argv, "--help"))
559         {
560           show_usage (0);
561         }
562       else if (!strcmp (*argv, "--verbose"))
563         {
564           verbose = 1;
565           argc--; argv++;
566         }
567       else if (!strcmp (*argv, "--no-list"))
568         {
569           no_list = 1;
570           argc--; argv++;
571         }
572       else if (!strcmp (*argv, "--mem-only"))
573         {
574           if (data_type)
575             {
576               show_usage (1);
577             }
578           mem_only = 1;
579           argc--; argv++;
580         }
581       else if (!strcmp (*argv, "--threads"))
582         {
583           argc--; argv++;
584           if (!argc)
585             {
586               show_usage (1);
587             }
588           threads = atoi (*argv);
589           if (!threads)
590             {
591               show_usage (1);
592             }
593           argc--; argv++;
594         }
595       else if (!strcmp (*argv, "--data-type"))
596         {
597           argc--; argv++;
598           if (!argc || mem_only)
599             {
600               show_usage (1);
601             }
602           data_type = atoi (*argv);
603           if (data_type > 4)
604             {
605               show_usage (1);
606             }
607           argc--; argv++;
608         }
609       else if (!strcmp (*argv, "--repeat"))
610         {
611           argc--; argv++;
612           if (!argc)
613             {
614               show_usage (1);
615             }
616           repeats = atoi (*argv);
617           if (!repeats)
618             {
619               show_usage (1);
620             }
621           argc--; argv++;
622         }
623     }
624
625   init_gpgme_basic ();
626
627   if (threads < argc)
628     {
629       /* Make sure we run once on each arg */
630       threads += argc;
631     }
632   while (argc)
633     {
634       if (!msgs)
635         {
636           msgs = calloc (1, sizeof *msgs);
637           msg_it = msgs;
638         }
639       else
640         {
641           msg_it->next = calloc (1, sizeof *msgs);
642           msg_it = msg_it->next;
643         }
644       msg_it->file_name = *argv;
645       argc--; argv++;
646     }
647
648   gpgrt_lock_lock (&threads_lock);
649   do
650     {
651       stop = 0;
652       thread_cnt = threads + 4;
653       out ("Repeats left: %i", repeats);
654
655       if (!no_list)
656         {
657           start_keylistings ();
658         }
659       else
660         {
661           thread_cnt -= 4;
662         }
663
664       while (thread_cnt)
665         {
666           log ("Thread %i", thread_cnt);
667           for (msg_it = msgs; msg_it && thread_cnt; msg_it = msg_it->next)
668             {
669               thread_cnt--;
670               create_thread (do_data_op, (void *)msg_it->file_name);
671             }
672         }
673
674       stop = 1;
675       gpgrt_lock_lock (&threads_lock);
676     }
677   while (--repeats != 0);
678
679   return 0;
680 }