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