Attempt to log an error if init failed
[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 void async_free_pool_internal(async_pool *pool);
74
75 static async_ctx *async_ctx_new(void)
76 {
77     async_ctx *nctx = NULL;
78
79     nctx = OPENSSL_malloc(sizeof (async_ctx));
80     if (nctx == NULL) {
81         ASYNCerr(ASYNC_F_ASYNC_CTX_NEW, ERR_R_MALLOC_FAILURE);
82         goto err;
83     }
84
85     async_fibre_init_dispatcher(&nctx->dispatcher);
86     nctx->currjob = NULL;
87     nctx->blocked = 0;
88     if (!async_set_ctx(nctx))
89         goto err;
90
91     return nctx;
92 err:
93     OPENSSL_free(nctx);
94
95     return NULL;
96 }
97
98 static async_ctx *async_get_ctx(void)
99 {
100     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL))
101         return NULL;
102     return async_arch_get_ctx();
103 }
104
105 static int async_ctx_free(void)
106 {
107     async_ctx *ctx;
108
109     ctx = async_get_ctx();
110
111     if (!async_set_ctx(NULL))
112         return 0;
113
114     OPENSSL_free(ctx);
115
116     return 1;
117 }
118
119 static ASYNC_JOB *async_job_new(void)
120 {
121     ASYNC_JOB *job = NULL;
122     OSSL_ASYNC_FD pipefds[2];
123
124     job = OPENSSL_malloc(sizeof (ASYNC_JOB));
125     if (job == NULL) {
126         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ERR_R_MALLOC_FAILURE);
127         return NULL;
128     }
129
130     if (!async_pipe(pipefds)) {
131         OPENSSL_free(job);
132         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ASYNC_R_CANNOT_CREATE_WAIT_PIPE);
133         return NULL;
134     }
135
136     job->wake_set = 0;
137     job->wait_fd = pipefds[0];
138     job->wake_fd = pipefds[1];
139
140     job->status = ASYNC_JOB_RUNNING;
141     job->funcargs = NULL;
142
143     return job;
144 }
145
146 static void async_job_free(ASYNC_JOB *job)
147 {
148     if (job != NULL) {
149         OPENSSL_free(job->funcargs);
150         async_fibre_free(&job->fibrectx);
151         async_close_fd(job->wait_fd);
152         async_close_fd(job->wake_fd);
153         OPENSSL_free(job);
154     }
155 }
156
157 static ASYNC_JOB *async_get_pool_job(void) {
158     ASYNC_JOB *job;
159     async_pool *pool;
160
161     pool = async_get_pool();
162     if (pool == NULL) {
163         /*
164          * Pool has not been initialised, so init with the defaults, i.e.
165          * no max size and no pre-created jobs
166          */
167         if (ASYNC_init_thread(0, 0) == 0)
168             return NULL;
169         pool = async_get_pool();
170     }
171
172     job = sk_ASYNC_JOB_pop(pool->jobs);
173     if (job == NULL) {
174         /* Pool is empty */
175         if ((pool->max_size != 0) && (pool->curr_size >= pool->max_size))
176             return NULL;
177
178         job = async_job_new();
179         if (job != NULL) {
180             if (! async_fibre_makecontext(&job->fibrectx)) {
181                 async_job_free(job);
182                 return NULL;
183             }
184             pool->curr_size++;
185         }
186     }
187     return job;
188 }
189
190 static void async_release_job(ASYNC_JOB *job) {
191     async_pool *pool;
192
193     pool = async_get_pool();
194     OPENSSL_free(job->funcargs);
195     job->funcargs = NULL;
196     sk_ASYNC_JOB_push(pool->jobs, job);
197 }
198
199 void async_start_func(void)
200 {
201     ASYNC_JOB *job;
202     async_ctx *ctx = async_get_ctx();
203
204     while (1) {
205         /* Run the job */
206         job = ctx->currjob;
207         job->ret = job->func(job->funcargs);
208
209         /* Stop the job */
210         job->status = ASYNC_JOB_STOPPING;
211         if (!async_fibre_swapcontext(&job->fibrectx,
212                                      &ctx->dispatcher, 1)) {
213             /*
214              * Should not happen. Getting here will close the thread...can't do
215              * much about it
216              */
217             ASYNCerr(ASYNC_F_ASYNC_START_FUNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
218         }
219     }
220 }
221
222 int ASYNC_start_job(ASYNC_JOB **job, int *ret, int (*func)(void *),
223                          void *args, size_t size)
224 {
225     async_ctx *ctx = async_get_ctx();
226     if (ctx == NULL)
227         ctx = async_ctx_new();
228     if (ctx == NULL) {
229         return ASYNC_ERR;
230     }
231
232     if (*job) {
233         ctx->currjob = *job;
234     }
235
236     for (;;) {
237         if (ctx->currjob != NULL) {
238             if (ctx->currjob->status == ASYNC_JOB_STOPPING) {
239                 *ret = ctx->currjob->ret;
240                 async_release_job(ctx->currjob);
241                 ctx->currjob = NULL;
242                 *job = NULL;
243                 return ASYNC_FINISH;
244             }
245
246             if (ctx->currjob->status == ASYNC_JOB_PAUSING) {
247                 *job = ctx->currjob;
248                 ctx->currjob->status = ASYNC_JOB_PAUSED;
249                 ctx->currjob = NULL;
250                 return ASYNC_PAUSE;
251             }
252
253             if (ctx->currjob->status == ASYNC_JOB_PAUSED) {
254                 ctx->currjob = *job;
255                 /* Resume previous job */
256                 if (!async_fibre_swapcontext(&ctx->dispatcher,
257                         &ctx->currjob->fibrectx, 1)) {
258                     ASYNCerr(ASYNC_F_ASYNC_START_JOB,
259                              ASYNC_R_FAILED_TO_SWAP_CONTEXT);
260                     goto err;
261                 }
262                 continue;
263             }
264
265             /* Should not happen */
266             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_INTERNAL_ERROR);
267             async_release_job(ctx->currjob);
268             ctx->currjob = NULL;
269             *job = NULL;
270             return ASYNC_ERR;
271         }
272
273         /* Start a new job */
274         if ((ctx->currjob = async_get_pool_job()) == NULL) {
275             return ASYNC_NO_JOBS;
276         }
277
278         if (args != NULL) {
279             ctx->currjob->funcargs = OPENSSL_malloc(size);
280             if (ctx->currjob->funcargs == NULL) {
281                 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_MALLOC_FAILURE);
282                 async_release_job(ctx->currjob);
283                 ctx->currjob = NULL;
284                 return ASYNC_ERR;
285             }
286             memcpy(ctx->currjob->funcargs, args, size);
287         } else {
288             ctx->currjob->funcargs = NULL;
289         }
290
291         ctx->currjob->func = func;
292         if (!async_fibre_swapcontext(&ctx->dispatcher,
293                 &ctx->currjob->fibrectx, 1)) {
294             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
295             goto err;
296         }
297     }
298
299 err:
300     async_release_job(ctx->currjob);
301     ctx->currjob = NULL;
302     *job = NULL;
303     return ASYNC_ERR;
304 }
305
306
307 int ASYNC_pause_job(void)
308 {
309     ASYNC_JOB *job;
310     async_ctx *ctx = async_get_ctx();
311
312     if (ctx == NULL
313             || ctx->currjob == NULL
314             || ctx->blocked) {
315         /*
316          * Could be we've deliberately not been started within a job so this is
317          * counted as success.
318          */
319         return 1;
320     }
321
322     job = ctx->currjob;
323     job->status = ASYNC_JOB_PAUSING;
324
325     if (!async_fibre_swapcontext(&job->fibrectx,
326                                  &ctx->dispatcher, 1)) {
327         ASYNCerr(ASYNC_F_ASYNC_PAUSE_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
328         return 0;
329     }
330
331     return 1;
332 }
333
334 static void async_empty_pool(async_pool *pool)
335 {
336     ASYNC_JOB *job;
337
338     if (!pool || !pool->jobs)
339         return;
340
341     do {
342         job = sk_ASYNC_JOB_pop(pool->jobs);
343         async_job_free(job);
344     } while (job);
345 }
346
347 int async_init(void)
348 {
349     if (!async_global_init())
350         return 0;
351
352     return 1;
353 }
354
355 int ASYNC_init_thread(size_t max_size, size_t init_size)
356 {
357     async_pool *pool;
358     size_t curr_size = 0;
359
360     if (init_size > max_size) {
361         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_INVALID_POOL_SIZE);
362         return 0;
363     }
364
365     if (!OPENSSL_init_crypto(OPENSSL_INIT_ASYNC, NULL)) {
366         return 0;
367     }
368     if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ASYNC)) {
369         return 0;
370     }
371
372     pool = OPENSSL_zalloc(sizeof *pool);
373     if (pool == NULL) {
374         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
375         return 0;
376     }
377
378     pool->jobs = sk_ASYNC_JOB_new_null();
379     if (pool->jobs == NULL) {
380         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
381         OPENSSL_free(pool);
382         return 0;
383     }
384
385     pool->max_size = max_size;
386
387     /* Pre-create jobs as required */
388     while (init_size--) {
389         ASYNC_JOB *job;
390         job = async_job_new();
391         if (job == NULL || !async_fibre_makecontext(&job->fibrectx)) {
392             /*
393              * Not actually fatal because we already created the pool, just
394              * skip creation of any more jobs
395              */
396             async_job_free(job);
397             break;
398         }
399         job->funcargs = NULL;
400         sk_ASYNC_JOB_push(pool->jobs, job);
401         curr_size++;
402     }
403     pool->curr_size = curr_size;
404     if (!async_set_pool(pool)) {
405         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_FAILED_TO_SET_POOL);
406         goto err;
407     }
408
409     return 1;
410 err:
411     async_free_pool_internal(pool);
412     return 0;
413 }
414
415 static void async_free_pool_internal(async_pool *pool)
416 {
417     if (pool == NULL)
418         return;
419
420     async_empty_pool(pool);
421     sk_ASYNC_JOB_free(pool->jobs);
422     OPENSSL_free(pool);
423     (void)async_set_pool(NULL);
424     async_local_cleanup();
425     async_ctx_free();
426 }
427
428 void ASYNC_cleanup_thread(void)
429 {
430     async_free_pool_internal(async_get_pool());
431 }
432
433 ASYNC_JOB *ASYNC_get_current_job(void)
434 {
435     async_ctx *ctx;
436
437     ctx = async_get_ctx();
438     if(ctx == NULL)
439         return NULL;
440
441     return ctx->currjob;
442 }
443
444 OSSL_ASYNC_FD ASYNC_get_wait_fd(ASYNC_JOB *job)
445 {
446     return job->wait_fd;
447 }
448
449 void ASYNC_wake(ASYNC_JOB *job)
450 {
451     char dummy = 0;
452
453     if (job->wake_set)
454         return;
455     async_write1(job->wake_fd, &dummy);
456     job->wake_set = 1;
457 }
458
459 void ASYNC_clear_wake(ASYNC_JOB *job)
460 {
461     char dummy = 0;
462     if (!job->wake_set)
463         return;
464     async_read1(job->wait_fd, &dummy);
465     job->wake_set = 0;
466 }
467
468 void ASYNC_block_pause(void)
469 {
470     async_ctx *ctx = async_get_ctx();
471     if (ctx == NULL || ctx->currjob == NULL) {
472         /*
473          * We're not in a job anyway so ignore this
474          */
475         return;
476     }
477     ctx->blocked++;
478 }
479
480 void ASYNC_unblock_pause(void)
481 {
482     async_ctx *ctx = async_get_ctx();
483     if (ctx == NULL || ctx->currjob == NULL) {
484         /*
485          * We're not in a job anyway so ignore this
486          */
487         return;
488     }
489     if(ctx->blocked > 0)
490         ctx->blocked--;
491 }