The new init functions can now fail so shouldn't be void
[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         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_NOT_INITED);
367         return 0;
368     }
369     if (!ossl_init_thread_start(OPENSSL_INIT_THREAD_ASYNC)) {
370         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_NOT_INITED);
371         return 0;
372     }
373
374     pool = OPENSSL_zalloc(sizeof *pool);
375     if (pool == NULL) {
376         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
377         return 0;
378     }
379
380     pool->jobs = sk_ASYNC_JOB_new_null();
381     if (pool->jobs == NULL) {
382         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
383         OPENSSL_free(pool);
384         return 0;
385     }
386
387     pool->max_size = max_size;
388
389     /* Pre-create jobs as required */
390     while (init_size--) {
391         ASYNC_JOB *job;
392         job = async_job_new();
393         if (job == NULL || !async_fibre_makecontext(&job->fibrectx)) {
394             /*
395              * Not actually fatal because we already created the pool, just
396              * skip creation of any more jobs
397              */
398             async_job_free(job);
399             break;
400         }
401         job->funcargs = NULL;
402         sk_ASYNC_JOB_push(pool->jobs, job);
403         curr_size++;
404     }
405     pool->curr_size = curr_size;
406     if (!async_set_pool(pool)) {
407         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_FAILED_TO_SET_POOL);
408         goto err;
409     }
410
411     return 1;
412 err:
413     async_free_pool_internal(pool);
414     return 0;
415 }
416
417 static void async_free_pool_internal(async_pool *pool)
418 {
419     if (pool == NULL)
420         return;
421
422     async_empty_pool(pool);
423     sk_ASYNC_JOB_free(pool->jobs);
424     OPENSSL_free(pool);
425     (void)async_set_pool(NULL);
426     async_local_cleanup();
427     async_ctx_free();
428 }
429
430 void ASYNC_cleanup_thread(void)
431 {
432     async_free_pool_internal(async_get_pool());
433 }
434
435 ASYNC_JOB *ASYNC_get_current_job(void)
436 {
437     async_ctx *ctx;
438
439     ctx = async_get_ctx();
440     if(ctx == NULL)
441         return NULL;
442
443     return ctx->currjob;
444 }
445
446 OSSL_ASYNC_FD ASYNC_get_wait_fd(ASYNC_JOB *job)
447 {
448     return job->wait_fd;
449 }
450
451 void ASYNC_wake(ASYNC_JOB *job)
452 {
453     char dummy = 0;
454
455     if (job->wake_set)
456         return;
457     async_write1(job->wake_fd, &dummy);
458     job->wake_set = 1;
459 }
460
461 void ASYNC_clear_wake(ASYNC_JOB *job)
462 {
463     char dummy = 0;
464     if (!job->wake_set)
465         return;
466     async_read1(job->wait_fd, &dummy);
467     job->wake_set = 0;
468 }
469
470 void ASYNC_block_pause(void)
471 {
472     async_ctx *ctx = async_get_ctx();
473     if (ctx == NULL || ctx->currjob == NULL) {
474         /*
475          * We're not in a job anyway so ignore this
476          */
477         return;
478     }
479     ctx->blocked++;
480 }
481
482 void ASYNC_unblock_pause(void)
483 {
484     async_ctx *ctx = async_get_ctx();
485     if (ctx == NULL || ctx->currjob == NULL) {
486         /*
487          * We're not in a job anyway so ignore this
488          */
489         return;
490     }
491     if(ctx->blocked > 0)
492         ctx->blocked--;
493 }