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