Implement windows async thread local variable support
[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_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) {
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(int init_thread, size_t max_size, size_t init_size)
332 {
333     if (!async_global_init())
334         return 0;
335
336     if (init_thread)
337         return ASYNC_init_thread(max_size, init_size);
338
339     return 1;
340 }
341
342 int ASYNC_init_thread(size_t max_size, size_t init_size)
343 {
344     async_pool *pool;
345     size_t curr_size = 0;
346
347     if (init_size > max_size) {
348         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_INVALID_POOL_SIZE);
349         return 0;
350     }
351
352     if (!async_local_init()) {
353         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_INIT_FAILED);
354         return 0;
355     }
356     pool = OPENSSL_zalloc(sizeof *pool);
357     if (pool == NULL) {
358         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
359         return 0;
360     }
361
362     pool->jobs = sk_ASYNC_JOB_new_null();
363     if (pool->jobs == NULL) {
364         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ERR_R_MALLOC_FAILURE);
365         OPENSSL_free(pool);
366         return 0;
367     }
368
369     pool->max_size = max_size;
370
371     /* Pre-create jobs as required */
372     while (init_size) {
373         ASYNC_JOB *job;
374         job = async_job_new();
375         if (job) {
376             async_fibre_makecontext(&job->fibrectx);
377             job->funcargs = NULL;
378             sk_ASYNC_JOB_push(pool->jobs, job);
379             curr_size++;
380             init_size--;
381         } else {
382             /*
383              * Not actually fatal because we already created the pool, just skip
384              * creation of any more jobs
385              */
386             init_size = 0;
387         }
388     }
389     pool->curr_size = curr_size;
390     if (!async_set_pool(pool)) {
391         ASYNCerr(ASYNC_F_ASYNC_INIT_THREAD, ASYNC_R_FAILED_TO_SET_POOL);
392         goto err;
393     }
394
395     return 1;
396 err:
397     async_free_pool_internal(pool);
398     return 0;
399 }
400
401 static void async_free_pool_internal(async_pool *pool)
402 {
403     if (pool == NULL)
404         return;
405
406     async_empty_pool(pool);
407     sk_ASYNC_JOB_free(pool->jobs);
408     OPENSSL_free(pool);
409     (void)async_set_pool(NULL);
410     async_local_cleanup();
411     async_ctx_free();
412 }
413
414 void ASYNC_cleanup_thread(void)
415 {
416     async_free_pool_internal(async_get_pool());
417 }
418
419 void ASYNC_cleanup(int cleanupthread)
420 {
421     /*
422      * We don't actually have any global cleanup at the moment so just cleanup
423      * the thread
424      */
425     if (cleanupthread)
426         ASYNC_cleanup_thread();
427 }
428
429 ASYNC_JOB *ASYNC_get_current_job(void)
430 {
431     async_ctx *ctx;
432
433     ctx = async_get_ctx();
434     if(ctx == NULL)
435         return NULL;
436
437     return ctx->currjob;
438 }
439
440 OSSL_ASYNC_FD ASYNC_get_wait_fd(ASYNC_JOB *job)
441 {
442     return job->wait_fd;
443 }
444
445 void ASYNC_wake(ASYNC_JOB *job)
446 {
447     char dummy = 0;
448
449     if (job->wake_set)
450         return;
451     async_write1(job->wake_fd, &dummy);
452     job->wake_set = 1;
453 }
454
455 void ASYNC_clear_wake(ASYNC_JOB *job)
456 {
457     char dummy = 0;
458     if (!job->wake_set)
459         return;
460     async_read1(job->wait_fd, &dummy);
461     job->wake_set = 0;
462 }
463
464 void ASYNC_block_pause(void)
465 {
466     if (async_get_ctx() == NULL
467             || async_get_ctx()->currjob == NULL) {
468         /*
469          * We're not in a job anyway so ignore this
470          */
471         return;
472     }
473     async_get_ctx()->blocked++;
474 }
475
476 void ASYNC_unblock_pause(void)
477 {
478     if (async_get_ctx() == NULL
479             || async_get_ctx()->currjob == NULL) {
480         /*
481          * We're not in a job anyway so ignore this
482          */
483         return;
484     }
485     if(async_get_ctx()->blocked > 0)
486         async_get_ctx()->blocked--;
487 }