Swap to using proper windows pipes
[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     nctx = OPENSSL_malloc(sizeof (async_ctx));
77     if (nctx == NULL) {
78         ASYNCerr(ASYNC_F_ASYNC_CTX_NEW, ERR_R_MALLOC_FAILURE);
79         goto err;
80     }
81
82     async_fibre_init_dispatcher(&nctx->dispatcher);
83     nctx->currjob = NULL;
84     nctx->blocked = 0;
85     if (!async_set_ctx(nctx))
86         goto err;
87
88     return nctx;
89 err:
90     OPENSSL_free(nctx);
91
92     return NULL;
93 }
94
95 static int async_ctx_free(void)
96 {
97     async_ctx *ctx;
98
99     ctx = async_get_ctx();
100
101     if (!async_set_ctx(NULL))
102         return 0;
103
104     OPENSSL_free(ctx);
105
106     return 1;
107 }
108
109 static ASYNC_JOB *async_job_new(void)
110 {
111     ASYNC_JOB *job = NULL;
112     OSSL_ASYNC_FD pipefds[2];
113
114     job = OPENSSL_malloc(sizeof (ASYNC_JOB));
115     if (job == NULL) {
116         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ERR_R_MALLOC_FAILURE);
117         return NULL;
118     }
119
120     if (!async_pipe(pipefds)) {
121         OPENSSL_free(job);
122         ASYNCerr(ASYNC_F_ASYNC_JOB_NEW, ASYNC_R_CANNOT_CREATE_WAIT_PIPE);
123         return NULL;
124     }
125
126     job->wake_set = 0;
127     job->wait_fd = pipefds[0];
128     job->wake_fd = pipefds[1];
129
130     job->status = ASYNC_JOB_RUNNING;
131     job->funcargs = NULL;
132
133     return job;
134 }
135
136 static void async_job_free(ASYNC_JOB *job)
137 {
138     if (job != NULL) {
139         OPENSSL_free(job->funcargs);
140         async_fibre_free(&job->fibrectx);
141         OPENSSL_free(job);
142     }
143 }
144
145 static ASYNC_JOB *async_get_pool_job(void) {
146     ASYNC_JOB *job;
147     STACK_OF(ASYNC_JOB) *pool;
148
149     pool = async_get_pool();
150     if (pool == NULL) {
151         /*
152          * Pool has not been initialised, so init with the defaults, i.e.
153          * no max size and no pre-created jobs
154          */
155         if (ASYNC_init_pool(0, 0) == 0)
156             return NULL;
157         pool = async_get_pool();
158     }
159
160     job = sk_ASYNC_JOB_pop(pool);
161     if (job == NULL) {
162         /* Pool is empty */
163         if (!async_pool_can_grow())
164             return NULL;
165
166         job = async_job_new();
167         if (job) {
168             async_fibre_makecontext(&job->fibrectx);
169             async_increment_pool_size();
170         }
171     }
172     return job;
173 }
174
175 static void async_release_job(ASYNC_JOB *job) {
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() == NULL && async_ctx_new() == NULL) {
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 != NULL) {
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()) == NULL) {
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 == NULL) {
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
383     ctx = async_get_ctx();
384     if(ctx == NULL)
385         return NULL;
386
387     return ctx->currjob;
388 }
389
390 OSSL_ASYNC_FD ASYNC_get_wait_fd(ASYNC_JOB *job)
391 {
392     return job->wait_fd;
393 }
394
395 void ASYNC_wake(ASYNC_JOB *job)
396 {
397     char dummy = 0;
398
399     if (job->wake_set)
400         return;
401     async_write1(job->wake_fd, &dummy);
402     job->wake_set = 1;
403 }
404
405 void ASYNC_clear_wake(ASYNC_JOB *job)
406 {
407     char dummy = 0;
408     if (!job->wake_set)
409         return;
410     async_read1(job->wait_fd, &dummy);
411     job->wake_set = 0;
412 }
413
414 void ASYNC_block_pause(void)
415 {
416     if (async_get_ctx() == NULL
417             || async_get_ctx()->currjob == NULL) {
418         /*
419          * We're not in a job anyway so ignore this
420          */
421         return;
422     }
423     async_get_ctx()->blocked++;
424 }
425
426 void ASYNC_unblock_pause(void)
427 {
428     if (async_get_ctx() == NULL
429             || async_get_ctx()->currjob == NULL) {
430         /*
431          * We're not in a job anyway so ignore this
432          */
433         return;
434     }
435     if(async_get_ctx()->blocked > 0)
436         async_get_ctx()->blocked--;
437 }