4a89499c7b07243c75ef933a22f6313b8ae554ec
[openssl.git] / crypto / async / async.c
1 /* crypto/async/async.c */
2 /*
3  * Written by Matt Caswell (matt@openssl.org) for the OpenSSL project.
4  */
5 /* ====================================================================
6  * Copyright (c) 2015 The OpenSSL Project.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  *
20  * 3. All advertising materials mentioning features or use of this
21  *    software must display the following acknowledgment:
22  *    "This product includes software developed by the OpenSSL Project
23  *    for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
24  *
25  * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
26  *    endorse or promote products derived from this software without
27  *    prior written permission. For written permission, please contact
28  *    licensing@OpenSSL.org.
29  *
30  * 5. Products derived from this software may not be called "OpenSSL"
31  *    nor may "OpenSSL" appear in their names without prior written
32  *    permission of the OpenSSL Project.
33  *
34  * 6. Redistributions of any form whatsoever must retain the following
35  *    acknowledgment:
36  *    "This product includes software developed by the OpenSSL Project
37  *    for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
38  *
39  * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
40  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
42  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
43  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
45  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
46  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
48  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
50  * OF THE POSSIBILITY OF SUCH DAMAGE.
51  * ====================================================================
52  */
53
54 /*
55  * Without this we start getting longjmp crashes because it thinks we're jumping
56  * up the stack when in fact we are jumping to an entirely different stack. The
57  * cost of this is not having certain buffer overrun/underrun checks etc for
58  * this source file :-(
59  */
60 #undef _FORTIFY_SOURCE
61
62 #include <openssl/err.h>
63 #include <openssl/async.h>
64 #include <string.h>
65 #include "async_locl.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_pool(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) {
172             async_fibre_makecontext(&job->fibrectx);
173             pool->curr_size++;
174         }
175     }
176     return job;
177 }
178
179 static void async_release_job(ASYNC_JOB *job) {
180     async_pool *pool;
181
182     pool = async_get_pool();
183     OPENSSL_free(job->funcargs);
184     job->funcargs = NULL;
185     sk_ASYNC_JOB_push(pool->jobs, job);
186 }
187
188 void async_start_func(void)
189 {
190     ASYNC_JOB *job;
191
192     while (1) {
193         /* Run the job */
194         job = async_get_ctx()->currjob;
195         job->ret = job->func(job->funcargs);
196
197         /* Stop the job */
198         job->status = ASYNC_JOB_STOPPING;
199         if (!async_fibre_swapcontext(&job->fibrectx,
200                                      &async_get_ctx()->dispatcher, 1)) {
201             /*
202              * Should not happen. Getting here will close the thread...can't do
203              * much about it
204              */
205             ASYNCerr(ASYNC_F_ASYNC_START_FUNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
206         }
207     }
208 }
209
210 int ASYNC_start_job(ASYNC_JOB **job, int *ret, int (*func)(void *),
211                          void *args, size_t size)
212 {
213     if (async_get_ctx() == NULL && async_ctx_new() == NULL) {
214         return ASYNC_ERR;
215     }
216
217     if (*job) {
218         async_get_ctx()->currjob = *job;
219     }
220
221     for (;;) {
222         if (async_get_ctx()->currjob != NULL) {
223             if (async_get_ctx()->currjob->status == ASYNC_JOB_STOPPING) {
224                 *ret = async_get_ctx()->currjob->ret;
225                 async_release_job(async_get_ctx()->currjob);
226                 async_get_ctx()->currjob = NULL;
227                 *job = NULL;
228                 return ASYNC_FINISH;
229             }
230
231             if (async_get_ctx()->currjob->status == ASYNC_JOB_PAUSING) {
232                 *job = async_get_ctx()->currjob;
233                 async_get_ctx()->currjob->status = ASYNC_JOB_PAUSED;
234                 async_get_ctx()->currjob = NULL;
235                 return ASYNC_PAUSE;
236             }
237
238             if (async_get_ctx()->currjob->status == ASYNC_JOB_PAUSED) {
239                 async_get_ctx()->currjob = *job;
240                 /* Resume previous job */
241                 if (!async_fibre_swapcontext(&async_get_ctx()->dispatcher,
242                         &async_get_ctx()->currjob->fibrectx, 1)) {
243                     ASYNCerr(ASYNC_F_ASYNC_START_JOB,
244                              ASYNC_R_FAILED_TO_SWAP_CONTEXT);
245                     goto err;
246                 }
247                 continue;
248             }
249
250             /* Should not happen */
251             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_INTERNAL_ERROR);
252             async_release_job(async_get_ctx()->currjob);
253             async_get_ctx()->currjob = NULL;
254             *job = NULL;
255             return ASYNC_ERR;
256         }
257
258         /* Start a new job */
259         if ((async_get_ctx()->currjob = async_get_pool_job()) == NULL) {
260             return ASYNC_NO_JOBS;
261         }
262
263         if (args != NULL) {
264             async_get_ctx()->currjob->funcargs = OPENSSL_malloc(size);
265             if (async_get_ctx()->currjob->funcargs == NULL) {
266                 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_MALLOC_FAILURE);
267                 async_release_job(async_get_ctx()->currjob);
268                 async_get_ctx()->currjob = NULL;
269                 return ASYNC_ERR;
270             }
271             memcpy(async_get_ctx()->currjob->funcargs, args, size);
272         } else {
273             async_get_ctx()->currjob->funcargs = NULL;
274         }
275
276         async_get_ctx()->currjob->func = func;
277         if (!async_fibre_swapcontext(&async_get_ctx()->dispatcher,
278                 &async_get_ctx()->currjob->fibrectx, 1)) {
279             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
280             goto err;
281         }
282     }
283
284 err:
285     async_release_job(async_get_ctx()->currjob);
286     async_get_ctx()->currjob = NULL;
287     *job = NULL;
288     return ASYNC_ERR;
289 }
290
291
292 int ASYNC_pause_job(void)
293 {
294     ASYNC_JOB *job;
295
296     if (async_get_ctx() == NULL
297             || async_get_ctx()->currjob == NULL
298             || async_get_ctx()->blocked) {
299         /*
300          * Could be we've deliberately not been started within a job so this is
301          * counted as success.
302          */
303         return 1;
304     }
305
306     job = async_get_ctx()->currjob;
307     job->status = ASYNC_JOB_PAUSING;
308
309     if (!async_fibre_swapcontext(&job->fibrectx,
310                                  &async_get_ctx()->dispatcher, 1)) {
311         ASYNCerr(ASYNC_F_ASYNC_PAUSE_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
312         return 0;
313     }
314
315     return 1;
316 }
317
318 static void async_empty_pool(async_pool *pool)
319 {
320     ASYNC_JOB *job;
321
322     if (!pool || !pool->jobs)
323         return;
324
325     do {
326         job = sk_ASYNC_JOB_pop(pool->jobs);
327         async_job_free(job);
328     } while (job);
329 }
330
331 int ASYNC_init_pool(size_t max_size, size_t init_size)
332 {
333     async_pool *pool;
334     size_t curr_size = 0;
335
336     if (init_size > max_size || max_size == 0) {
337         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ASYNC_R_INVALID_POOL_SIZE);
338         return 0;
339     }
340
341     if(async_get_pool() != NULL) {
342         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ASYNC_R_POOL_ALREADY_INITED);
343         return 0;
344     }
345
346     pool = OPENSSL_zalloc(sizeof *pool);
347     if (pool == NULL) {
348         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ERR_R_MALLOC_FAILURE);
349         return 0;
350     }
351
352     pool->jobs = sk_ASYNC_JOB_new_null();
353     if (pool->jobs == NULL) {
354         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ERR_R_MALLOC_FAILURE);
355         OPENSSL_free(pool);
356         return 0;
357     }
358
359     pool->max_size = max_size;
360
361     /* Pre-create jobs as required */
362     while (init_size) {
363         ASYNC_JOB *job;
364         job = async_job_new();
365         if (job) {
366             async_fibre_makecontext(&job->fibrectx);
367             job->funcargs = NULL;
368             sk_ASYNC_JOB_push(pool->jobs, job);
369             curr_size++;
370             init_size--;
371         } else {
372             /*
373              * Not actually fatal because we already created the pool, just skip
374              * creation of any more jobs
375              */
376             init_size = 0;
377         }
378     }
379     pool->curr_size = curr_size;
380
381     if (!async_set_pool(pool)) {
382         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ASYNC_R_FAILED_TO_SET_POOL);
383         goto err;
384     }
385
386     return 1;
387 err:
388     async_free_pool_internal(pool);
389     return 0;
390 }
391
392 static void async_free_pool_internal(async_pool *pool)
393 {
394     if (pool == NULL)
395         return;
396
397     async_empty_pool(pool);
398     sk_ASYNC_JOB_free(pool->jobs);
399     OPENSSL_free(pool);
400     async_set_pool(NULL);
401     async_ctx_free();
402 }
403
404 void ASYNC_free_pool(void)
405 {
406     async_free_pool_internal(async_get_pool());
407 }
408
409 ASYNC_JOB *ASYNC_get_current_job(void)
410 {
411     async_ctx *ctx;
412
413     ctx = async_get_ctx();
414     if(ctx == NULL)
415         return NULL;
416
417     return ctx->currjob;
418 }
419
420 OSSL_ASYNC_FD ASYNC_get_wait_fd(ASYNC_JOB *job)
421 {
422     return job->wait_fd;
423 }
424
425 void ASYNC_wake(ASYNC_JOB *job)
426 {
427     char dummy = 0;
428
429     if (job->wake_set)
430         return;
431     async_write1(job->wake_fd, &dummy);
432     job->wake_set = 1;
433 }
434
435 void ASYNC_clear_wake(ASYNC_JOB *job)
436 {
437     char dummy = 0;
438     if (!job->wake_set)
439         return;
440     async_read1(job->wait_fd, &dummy);
441     job->wake_set = 0;
442 }
443
444 void ASYNC_block_pause(void)
445 {
446     if (async_get_ctx() == NULL
447             || async_get_ctx()->currjob == NULL) {
448         /*
449          * We're not in a job anyway so ignore this
450          */
451         return;
452     }
453     async_get_ctx()->blocked++;
454 }
455
456 void ASYNC_unblock_pause(void)
457 {
458     if (async_get_ctx() == NULL
459             || async_get_ctx()->currjob == NULL) {
460         /*
461          * We're not in a job anyway so ignore this
462          */
463         return;
464     }
465     if(async_get_ctx()->blocked > 0)
466         async_get_ctx()->blocked--;
467 }