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