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