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