Fix Linux crash
[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 async_ctx *async_ctx_new(void)
73 {
74     async_ctx *nctx = NULL;
75
76     if(!(nctx = OPENSSL_malloc(sizeof (async_ctx)))) {
77         ASYNCerr(ASYNC_F_ASYNC_CTX_NEW, ERR_R_MALLOC_FAILURE);
78         goto err;
79     }
80
81     async_fibre_init_dispatcher(&nctx->dispatcher);
82     nctx->currjob = NULL;
83     if(!async_set_ctx(nctx))
84         goto err;
85
86     return nctx;
87 err:
88     if(nctx) {
89         OPENSSL_free(nctx);
90     }
91
92     return NULL;
93 }
94
95 static int async_ctx_free(void)
96 {
97     if(async_get_ctx()) {
98         OPENSSL_free(async_get_ctx());
99     }
100
101     if(!async_set_ctx(NULL))
102         return 0;
103
104     return 1;
105 }
106
107 static ASYNC_JOB *async_job_new(void)
108 {
109     ASYNC_JOB *job = NULL;
110     int pipefds[2];
111
112     if(!(job = OPENSSL_malloc(sizeof (ASYNC_JOB)))) {
113         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ERR_R_MALLOC_FAILURE);
114         return NULL;
115     }
116
117     if(!async_pipe(pipefds)) {
118         OPENSSL_free(job);
119         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ASYNC_R_CANNOT_CREATE_WAIT_PIPE);
120         return NULL;
121     }
122
123     job->wake_set = 0;
124     job->wait_fd = pipefds[0];
125     job->wake_fd = pipefds[1];
126
127     job->status = ASYNC_JOB_RUNNING;
128     job->funcargs = NULL;
129
130     return job;
131 }
132
133 static void async_job_free(ASYNC_JOB *job)
134 {
135     if(job) {
136         if(job->funcargs)
137             OPENSSL_free(job->funcargs);
138         async_fibre_free(&job->fibrectx);
139         OPENSSL_free(job);
140     }
141 }
142
143 static ASYNC_JOB *async_get_pool_job(void) {
144     ASYNC_JOB *job;
145     STACK_OF(ASYNC_JOB) *pool;
146
147     pool = async_get_pool();
148     if (pool == NULL) {
149         /*
150          * Pool has not been initialised, so init with the defaults, i.e.
151          * no max size and no pre-created jobs
152          */
153         if (ASYNC_init_pool(0, 0) == 0)
154             return NULL;
155         pool = async_get_pool();
156     }
157
158     job = sk_ASYNC_JOB_pop(pool);
159     if (job == NULL) {
160         /* Pool is empty */
161         if (!async_pool_can_grow())
162             return NULL;
163
164         job = async_job_new();
165         if (job) {
166             async_fibre_makecontext(&job->fibrectx);
167             async_increment_pool_size();
168         }
169     }
170     return job;
171 }
172
173 static void async_release_job(ASYNC_JOB *job) {
174     if(job->funcargs)
175         OPENSSL_free(job->funcargs);
176     job->funcargs = NULL;
177     /* Ignore error return */
178     async_release_job_to_pool(job);
179 }
180
181 void async_start_func(void)
182 {
183     ASYNC_JOB *job;
184
185     while (1) {
186         /* Run the job */
187         job = async_get_ctx()->currjob;
188         job->ret = job->func(job->funcargs);
189
190         /* Stop the job */
191         job->status = ASYNC_JOB_STOPPING;
192         if(!async_fibre_swapcontext(&job->fibrectx,
193                                     &async_get_ctx()->dispatcher, 1)) {
194             /*
195              * Should not happen. Getting here will close the thread...can't do
196              * much about it
197              */
198             ASYNCerr(ASYNC_F_ASYNC_START_FUNC, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
199         }
200     }
201 }
202
203 int ASYNC_start_job(ASYNC_JOB **job, int *ret, int (*func)(void *),
204                          void *args, size_t size)
205 {
206     if(!async_get_ctx() && !async_ctx_new()) {
207         return ASYNC_ERR;
208     }
209
210     if(*job) {
211         async_get_ctx()->currjob = *job;
212     }
213
214     for (;;) {
215         if(async_get_ctx()->currjob) {
216             if(async_get_ctx()->currjob->status == ASYNC_JOB_STOPPING) {
217                 *ret = async_get_ctx()->currjob->ret;
218                 async_release_job(async_get_ctx()->currjob);
219                 async_get_ctx()->currjob = NULL;
220                 *job = NULL;
221                 return ASYNC_FINISH;
222             }
223
224             if(async_get_ctx()->currjob->status == ASYNC_JOB_PAUSING) {
225                 *job = async_get_ctx()->currjob;
226                 async_get_ctx()->currjob->status = ASYNC_JOB_PAUSED;
227                 async_get_ctx()->currjob = NULL;
228                 return ASYNC_PAUSE;
229             }
230
231             if(async_get_ctx()->currjob->status == ASYNC_JOB_PAUSED) {
232                 async_get_ctx()->currjob = *job;
233                 /* Resume previous job */
234                 if(!async_fibre_swapcontext(&async_get_ctx()->dispatcher,
235                     &async_get_ctx()->currjob->fibrectx, 1)) {
236                     ASYNCerr(ASYNC_F_ASYNC_START_JOB,
237                              ASYNC_R_FAILED_TO_SWAP_CONTEXT);
238                     goto err;
239                 }
240                 continue;
241             }
242
243             /* Should not happen */
244             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_INTERNAL_ERROR);
245             async_release_job(async_get_ctx()->currjob);
246             async_get_ctx()->currjob = NULL;
247             *job = NULL;
248             return ASYNC_ERR;
249         }
250
251         /* Start a new job */
252         if(!(async_get_ctx()->currjob = async_get_pool_job())) {
253             return ASYNC_NO_JOBS;
254         }
255
256         if(args != NULL) {
257             async_get_ctx()->currjob->funcargs = OPENSSL_malloc(size);
258             if(!async_get_ctx()->currjob->funcargs) {
259                 ASYNCerr(ASYNC_F_ASYNC_START_JOB, ERR_R_MALLOC_FAILURE);
260                 async_release_job(async_get_ctx()->currjob);
261                 async_get_ctx()->currjob = NULL;
262                 return ASYNC_ERR;
263             }
264             memcpy(async_get_ctx()->currjob->funcargs, args, size);
265         } else {
266             async_get_ctx()->currjob->funcargs = NULL;
267         }
268
269         async_get_ctx()->currjob->func = func;
270         if(!async_fibre_swapcontext(&async_get_ctx()->dispatcher,
271             &async_get_ctx()->currjob->fibrectx, 1)) {
272             ASYNCerr(ASYNC_F_ASYNC_START_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
273             goto err;
274         }
275     }
276
277 err:
278     async_release_job(async_get_ctx()->currjob);
279     async_get_ctx()->currjob = NULL;
280     *job = NULL;
281     return ASYNC_ERR;
282 }
283
284
285 int ASYNC_pause_job(void)
286 {
287     ASYNC_JOB *job;
288
289     if(!async_get_ctx() || !async_get_ctx()->currjob) {
290         /*
291          * Could be we've deliberately not been started within a job so this is
292          * counted as success.
293          */
294         return 1;
295     }
296
297     job = async_get_ctx()->currjob;
298     job->status = ASYNC_JOB_PAUSING;
299
300     if(!async_fibre_swapcontext(&job->fibrectx,
301                                &async_get_ctx()->dispatcher, 1)) {
302         ASYNCerr(ASYNC_F_ASYNC_PAUSE_JOB, ASYNC_R_FAILED_TO_SWAP_CONTEXT);
303         return 0;
304     }
305
306     return 1;
307 }
308
309 static void async_empty_pool(STACK_OF(ASYNC_JOB) *pool)
310 {
311     ASYNC_JOB *job;
312
313     do {
314         job = sk_ASYNC_JOB_pop(pool);
315         async_job_free(job);
316     } while (job);
317 }
318
319 int ASYNC_init_pool(size_t max_size, size_t init_size)
320 {
321     STACK_OF(ASYNC_JOB) *pool;
322     size_t curr_size = 0;
323
324     if (init_size > max_size) {
325         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ASYNC_R_INVALID_POOL_SIZE);
326         return 0;
327     }
328
329     pool = sk_ASYNC_JOB_new_null();
330     if (pool == NULL) {
331         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ERR_R_MALLOC_FAILURE);
332         return 0;
333     }
334     /* Pre-create jobs as required */
335     while (init_size) {
336         ASYNC_JOB *job;
337         job = async_job_new();
338         if (job) {
339             async_fibre_makecontext(&job->fibrectx);
340             job->funcargs = NULL;
341             sk_ASYNC_JOB_push(pool, job);
342             curr_size++;
343             init_size--;
344         } else {
345             /*
346              * Not actually fatal because we already created the pool, just skip
347              * creation of any more jobs
348              */
349             init_size = 0;
350         }
351     }
352
353     if (!async_set_pool(pool, curr_size, max_size)) {
354         ASYNCerr(ASYNC_F_ASYNC_INIT_POOL, ASYNC_R_FAILED_TO_SET_POOL);
355         async_empty_pool(pool);
356         sk_ASYNC_JOB_free(pool);
357         return 0;
358     }
359
360     return 1;
361 }
362
363 void ASYNC_free_pool(void)
364 {
365     STACK_OF(ASYNC_JOB) *pool;
366
367     pool = async_get_pool();
368     if (pool == NULL)
369         return;
370
371     async_empty_pool(pool);
372     async_release_pool();
373     async_ctx_free();
374 }
375
376 ASYNC_JOB *ASYNC_get_current_job(void)
377 {
378     async_ctx *ctx;
379     if((ctx = async_get_ctx()) == NULL)
380         return NULL;
381
382     return ctx->currjob;
383 }
384
385 int ASYNC_get_wait_fd(ASYNC_JOB *job)
386 {
387     return job->wait_fd;
388 }
389
390 void ASYNC_wake(ASYNC_JOB *job)
391 {
392     char dummy = 0;
393
394     if (job->wake_set)
395         return;
396     async_write1(job->wake_fd, &dummy);
397     job->wake_set = 1;
398 }
399
400 void ASYNC_clear_wake(ASYNC_JOB *job)
401 {
402     char dummy = 0;
403     if (!job->wake_set)
404         return;
405     async_read1(job->wait_fd, &dummy);
406     job->wake_set = 0;
407 }