Fold threads.h into crypto.h making API public
[oweals/openssl.git] / crypto / async / async.c
1 /*
2  * Written by Matt Caswell (matt@openssl.org) for the OpenSSL project.
3  */
4 /* ====================================================================
5  * Copyright (c) 2015 The OpenSSL Project.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. All advertising materials mentioning features or use of this
20  *    software must display the following acknowledgment:
21  *    "This product includes software developed by the OpenSSL Project
22  *    for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
23  *
24  * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
25  *    endorse or promote products derived from this software without
26  *    prior written permission. For written permission, please contact
27  *    licensing@OpenSSL.org.
28  *
29  * 5. Products derived from this software may not be called "OpenSSL"
30  *    nor may "OpenSSL" appear in their names without prior written
31  *    permission of the OpenSSL Project.
32  *
33  * 6. Redistributions of any form whatsoever must retain the following
34  *    acknowledgment:
35  *    "This product includes software developed by the OpenSSL Project
36  *    for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
37  *
38  * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
39  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
40  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
41  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
42  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
43  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
44  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
45  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
47  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
48  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
49  * OF THE POSSIBILITY OF SUCH DAMAGE.
50  * ====================================================================
51  */
52
53 /*
54  * Without this we start getting longjmp crashes because it thinks we're jumping
55  * up the stack when in fact we are jumping to an entirely different stack. The
56  * cost of this is not having certain buffer overrun/underrun checks etc for
57  * this source file :-(
58  */
59 #undef _FORTIFY_SOURCE
60
61 /* This must be the first #include file */
62 #include "async_locl.h"
63
64 #include <openssl/err.h>
65 #include <internal/cryptlib_int.h>
66 #include <string.h>
67
68 #define ASYNC_JOB_RUNNING   0
69 #define ASYNC_JOB_PAUSING   1
70 #define ASYNC_JOB_PAUSED    2
71 #define ASYNC_JOB_STOPPING  3
72
73 static CRYPTO_THREAD_LOCAL ctxkey;
74 static CRYPTO_THREAD_LOCAL poolkey;
75
76 static void async_free_pool_internal(async_pool *pool);
77
78 static async_ctx *async_ctx_new(void)
79 {
80     async_ctx *nctx = NULL;
81
82     nctx = OPENSSL_malloc(sizeof (async_ctx));
83     if (nctx == NULL) {
84         ASYNCerr(ASYNC_F_ASYNC_CTX_NEW, ERR_R_MALLOC_FAILURE);
85         goto err;
86     }
87
88     async_fibre_init_dispatcher(&nctx->dispatcher);
89     nctx->currjob = NULL;
90     nctx->blocked = 0;
91     if (!CRYPTO_THREAD_set_local(&ctxkey, nctx))
92         goto err;
93
94     return nctx;
95 err:
96     OPENSSL_free(nctx);
97
98     return NULL;
99 }
100
101 async_ctx *async_get_ctx(void)
102 {
103     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
104         return NULL;
105
106     return (async_ctx *)CRYPTO_THREAD_get_local(&ctxkey);
107 }
108
109 static int async_ctx_free(void)
110 {
111     async_ctx *ctx;
112
113     ctx = async_get_ctx();
114
115     if (!CRYPTO_THREAD_set_local(&ctxkey, NULL))
116         return 0;
117
118     OPENSSL_free(ctx);
119
120     return 1;
121 }
122
123 static ASYNC_JOB *async_job_new(void)
124 {
125     ASYNC_JOB *job = NULL;
126
127     job = OPENSSL_zalloc(sizeof (ASYNC_JOB));
128     if (job == NULL) {
129         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ERR_R_MALLOC_FAILURE);
130         return NULL;
131     }
132
133     job->status = ASYNC_JOB_RUNNING;
134
135     return job;
136 }
137
138 static void async_job_free(ASYNC_JOB *job)
139 {
140     if (job != NULL) {
141         OPENSSL_free(job->funcargs);
142         async_fibre_free(&job->fibrectx);
143         OPENSSL_free(job);
144     }
145 }
146
147 static ASYNC_JOB *async_get_pool_job(void) {
148     ASYNC_JOB *job;
149     async_pool *pool;
150
151     pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
152     if (pool == NULL) {
153         /*
154          * Pool has not been initialised, so init with the defaults, i.e.
155          * no max size and no pre-created jobs
156          */
157         if (ASYNC_init_thread(0, 0) == 0)
158             return NULL;
159         pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
160     }
161
162     job = sk_ASYNC_JOB_pop(pool->jobs);
163     if (job == NULL) {
164         /* Pool is empty */
165         if ((pool->max_size != 0) && (pool->curr_size >= pool->max_size))
166             return NULL;
167
168         job = async_job_new();
169         if (job != NULL) {
170             if (! async_fibre_makecontext(&job->fibrectx)) {
171                 async_job_free(job);
172                 return NULL;
173             }
174             pool->curr_size++;
175         }
176     }
177     return job;
178 }
179
180 static void async_release_job(ASYNC_JOB *job) {
181     async_pool *pool;
182
183     pool = (async_pool *)CRYPTO_THREAD_get_local(&poolkey);
184     OPENSSL_free(job->funcargs);
185     job->funcargs = NULL;
186     sk_ASYNC_JOB_push(pool->jobs, job);
187 }
188
189 void async_start_func(void)
190 {
191     ASYNC_JOB *job;
192     async_ctx *ctx = async_get_ctx();
193
194     while (1) {
195         /* Run the job */
196         job = ctx->currjob;
197         job->ret = job->func(job->funcargs);
198
199         /* Stop the job */
200         job->status = ASYNC_JOB_STOPPING;
201         if (!async_fibre_swapcontext(&job->fibrectx,
202                                      &ctx->dispatcher, 1)) {
203             /*
204              * Should not happen. Getting here will close the thread...can't do
205              * much about it
206              */
207             ASYNCerr(ASYNC_F_ASYNC_START_FUNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
208         }
209     }
210 }
211
212 int ASYNC_start_job(ASYNC_JOB **job, ASYNC_WAIT_CTX *wctx, int *ret,
213                     int (*func)(void *), void *args, size_t size)
214 {
215     async_ctx *ctx = async_get_ctx();
216     if (ctx == NULL)
217         ctx = async_ctx_new();
218     if (ctx == NULL) {
219         return ASYNC_ERR;
220     }
221
222     if (*job) {
223         ctx->currjob = *job;
224     }
225
226     for (;;) {
227         if (ctx->currjob != NULL) {
228             if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
229                 *ret = ctx->currjob->ret;
230                 ctx->currjob->waitctx = NULL;
231                 async_release_job(ctx->currjob);
232                 ctx->currjob = NULL;
233                 *job = NULL;
234                 return ASYNC_FINISH;
235             }
236
237             if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
238                 *job = ctx->currjob;
239                 ctx->currjob->status = ASYNC_JOB_PAUSED;
240                 ctx->currjob = NULL;
241                 return ASYNC_PAUSE;
242             }
243
244             if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
245                 ctx->currjob = *job;
246                 /* Resume previous job */
247                 if (!async_fibre_swapcontext(&ctx->dispatcher,
248                         &ctx->currjob->fibrectx, 1)) {
249                     ASYNCerr(ASYNC_F_ASYNC_START_JOB,
250                              ASYNC_R_FAILED_TO_SWAP_CONTEXT);
251                     goto err;
252                 }
253                 continue;
254             }
255
256             /* Should not happen */
257             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_INTERNAL_ERROR);
258             async_release_job(ctx->currjob);
259             ctx->currjob = NULL;
260             *job = NULL;
261             return ASYNC_ERR;
262         }
263
264         /* Start a new job */
265         if ((ctx->currjob = async_get_pool_job()) == NULL) {
266             return ASYNC_NO_JOBS;
267         }
268
269         if (args != NULL) {
270             ctx->currjob->funcargs = OPENSSL_malloc(size);
271             if (ctx->currjob->funcargs == NULL) {
272                 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_MALLOC_FAILURE);
273                 async_release_job(ctx->currjob);
274                 ctx->currjob = NULL;
275                 return ASYNC_ERR;
276             }
277             memcpy(ctx->currjob->funcargs, args, size);
278         } else {
279             ctx->currjob->funcargs = NULL;
280         }
281
282         ctx->currjob->func = func;
283         ctx->currjob->waitctx = wctx;
284         if (!async_fibre_swapcontext(&ctx->dispatcher,
285                 &ctx->currjob->fibrectx, 1)) {
286             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
287             goto err;
288         }
289     }
290
291 err:
292     async_release_job(ctx->currjob);
293     ctx->currjob = NULL;
294     *job = NULL;
295     return ASYNC_ERR;
296 }
297
298 int ASYNC_pause_job(void)
299 {
300     ASYNC_JOB *job;
301     async_ctx *ctx = async_get_ctx();
302
303     if (ctx == NULL
304             || ctx->currjob == NULL
305             || ctx->blocked) {
306         /*
307          * Could be we've deliberately not been started within a job so this is
308          * counted as success.
309          */
310         return 1;
311     }
312
313     job = ctx->currjob;
314     job->status = ASYNC_JOB_PAUSING;
315
316     if (!async_fibre_swapcontext(&job->fibrectx,
317                                  &ctx->dispatcher, 1)) {
318         ASYNCerr(ASYNC_F_ASYNC_PAUSE_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
319         return 0;
320     }
321     /* Reset counts of added and deleted fds */
322     async_wait_ctx_reset_counts(job->waitctx);
323
324     return 1;
325 }
326
327 static void async_empty_pool(async_pool *pool)
328 {
329     ASYNC_JOB *job;
330
331     if (!pool || !pool->jobs)
332         return;
333
334     do {
335         job = sk_ASYNC_JOB_pop(pool->jobs);
336         async_job_free(job);
337     } while (job);
338 }
339
340 int async_init(void)
341 {
342     if (!CRYPTO_THREAD_init_local(&ctxkey, NULL))
343         return 0;
344
345     if (!CRYPTO_THREAD_init_local(&poolkey, NULL)) {
346         CRYPTO_THREAD_cleanup_local(&ctxkey);
347         return 0;
348     }
349
350     return 1;
351 }
352
353 void async_deinit(void)
354 {
355     CRYPTO_THREAD_cleanup_local(&ctxkey);
356     CRYPTO_THREAD_cleanup_local(&poolkey);
357 }
358
359 int ASYNC_init_thread(size_t max_size, size_t init_size)
360 {
361     async_pool *pool;
362     size_t curr_size = 0;
363
364     if (init_size > max_size) {
365         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_INVALID_POOL_SIZE);
366         return 0;
367     }
368
369     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL)) {
370         return 0;
371     }
372     if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ASYNC)) {
373         return 0;
374     }
375
376     pool = OPENSSL_zalloc(sizeof *pool);
377     if (pool == NULL) {
378         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
379         return 0;
380     }
381
382     pool->jobs = sk_ASYNC_JOB_new_null();
383     if (pool->jobs == NULL) {
384         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
385         OPENSSL_free(pool);
386         return 0;
387     }
388
389     pool->max_size = max_size;
390
391     /* Pre-create jobs as required */
392     while (init_size--) {
393         ASYNC_JOB *job;
394         job = async_job_new();
395         if (job == NULL || !async_fibre_makecontext(&job->fibrectx)) {
396             /*
397              * Not actually fatal because we already created the pool, just
398              * skip creation of any more jobs
399              */
400             async_job_free(job);
401             break;
402         }
403         job->funcargs = NULL;
404         sk_ASYNC_JOB_push(pool->jobs, job);
405         curr_size++;
406     }
407     pool->curr_size = curr_size;
408     if (!CRYPTO_THREAD_set_local(&poolkey, pool)) {
409         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_FAILED_TO_SET_POOL);
410         goto err;
411     }
412
413     return 1;
414 err:
415     async_free_pool_internal(pool);
416     return 0;
417 }
418
419 static void async_free_pool_internal(async_pool *pool)
420 {
421     if (pool == NULL)
422         return;
423
424     async_empty_pool(pool);
425     sk_ASYNC_JOB_free(pool->jobs);
426     OPENSSL_free(pool);
427     CRYPTO_THREAD_set_local(&poolkey, NULL);
428     async_local_cleanup();
429     async_ctx_free();
430 }
431
432 void ASYNC_cleanup_thread(void)
433 {
434     async_free_pool_internal((async_pool *)CRYPTO_THREAD_get_local(&poolkey));
435 }
436
437 ASYNC_JOB *ASYNC_get_current_job(void)
438 {
439     async_ctx *ctx;
440
441     ctx = async_get_ctx();
442     if(ctx == NULL)
443         return NULL;
444
445     return ctx->currjob;
446 }
447
448 ASYNC_WAIT_CTX *ASYNC_get_wait_ctx(ASYNC_JOB *job)
449 {
450     return job->waitctx;
451 }
452
453 void ASYNC_block_pause(void)
454 {
455     async_ctx *ctx = async_get_ctx();
456     if (ctx == NULL || ctx->currjob == NULL) {
457         /*
458          * We're not in a job anyway so ignore this
459          */
460         return;
461     }
462     ctx->blocked++;
463 }
464
465 void ASYNC_unblock_pause(void)
466 {
467     async_ctx *ctx = async_get_ctx();
468     if (ctx == NULL || ctx->currjob == NULL) {
469         /*
470          * We're not in a job anyway so ignore this
471          */
472         return;
473     }
474     if(ctx->blocked > 0)
475         ctx->blocked--;
476 }