4158759a75b851e798e4992efb8d3c1df0c25bb8
[tlspool] / pulleyback / api.c
1 /* pulleyback/api.c -- The API to the Pulley backend for the TLS Pool
2  *
3  * From: Rick van Rein <rick@openfortress.nl>
4  */
5
6
7 #include <stdlib.h>
8 #include <string.h>
9 #include <assert.h>
10
11 #include <errno.h>
12
13 #include "api.h"
14
15 #include "poolback.h"
16
17
18 /* Allocate a handler, parse information into it, access the database.
19  */
20 void *pulleyback_open (int argc, char **argv, int varc) {
21         struct pulleyback_tlspool *self;
22         int rv;
23         //
24         // Allocate memory and wipe it clean
25         self = malloc (sizeof (struct pulleyback_tlspool));
26         if (self == NULL) {
27                 errno = ENOMEM;
28                 return NULL;
29         }
30         memset (self, 0, sizeof (struct pulleyback_tlspool));
31         //
32         // Parse arguments, connect to the database
33         rv = parse_arguments (argc, argv, varc, self);
34         if (rv != 0) {
35                 free (self);
36                 errno = EINVAL;
37                 return NULL;
38         }
39         errno = 0;
40         rv = open_database (self);
41         if (rv != 0) {
42                 free (self);
43                 if (errno == 0) {
44                         errno = ENXIO;
45                 }
46                 return NULL;
47         }
48         //
49         // Return the successful result -- mapped to a (void *)
50         return (void *) self;
51 }
52
53 void pulleyback_close (void *pbh) {
54         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
55         if (self == NULL) {
56                 return;
57         }
58         //
59         // Disconnect from the database after implicit rollback
60         if (self->txn_state != TXN_NONE) {
61                 pulleyback_rollback (pbh);
62         }
63         close_database (self);
64         //
65         // Cleanup fields allocated with strdup()
66         if (self->db_env != NULL) {
67                 free (self->db_env);
68                 self->db_env = NULL;
69         }
70         if (self->db_filename != NULL) {
71                 free (self->db_filename);
72                 self->db_filename = NULL;
73         }
74         //
75         // Free the basic data structure
76         free (self);
77         self = NULL;
78 }
79
80 /* Internal method to ensure having a transaction, return 0 on error
81  */
82 static int have_txn (struct pulleyback_tlspool *self) {
83         switch (self->txn_state) {
84         case TXN_NONE:
85                 if (0 != self->env->txn_begin (self->env, NULL, &self->txn, 0)) {
86                         return 0;
87                 }
88                 self->txn_state = TXN_ACTIVE;
89                 return 1;
90         case TXN_ACTIVE:
91                 return 1;
92         case TXN_SUCCESS:
93         case TXN_ABORT:
94                 // You cannot have_txn() after _prepare()
95                 assert ((self->txn_state == TXN_NONE) || (self->txn_state == TXN_ACTIVE));
96                 return 0;
97         }
98 }
99
100 int pulleyback_add (void *pbh, uint8_t **forkdata) {
101         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
102         int ok = 1;
103         ok = ok && have_txn (self);
104         ok = ok && (self->txn_state == TXN_ACTIVE);
105         ok = ok && self->update (self, forkdata, 0);
106         return ok;
107 }
108
109 int pulleyback_del (void *pbh, uint8_t **forkdata) {
110         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
111         int ok = 1;
112         ok = ok && have_txn (self);
113         ok = ok && (self->txn_state == TXN_ACTIVE);
114         ok = ok && self->update (self, forkdata, 1);
115         return ok;
116 }
117
118
119 int pulleyback_reset (void *pbh) {
120         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
121         int ok = 1;
122         u_int32_t count;
123         ok = ok && have_txn (self);
124         ok = ok && (self->txn_state == TXN_ACTIVE);
125         ok = ok && (0 == self->db->truncate (self->db, self->txn, &count, 0));
126         return ok;
127 }
128
129
130 /* Transactions are somewhat complex, also because they are implicitly
131  * started when changes are made using _add(), _del() or _reset().
132  * These operations may be conditional in the calling program, and we
133  * want to aliviete the burden of maintaining a state as to whether
134  * a transaction has been started implicitly, so we accept calls to
135  * _prepare(), _rollback() or _commit() when no transaction exists
136  * yet.  We will implement such occurrences equivalently to opening a
137  * new transaction and acting on it immediately (though the logs may
138  * not show it if we can optimise by not actually doing it -- it is
139  * much simpler to skip the _rollback() or _commit() on an empty
140  * transaction.
141  *
142  * We use txn_state to maintain state between _prepare() and its followup
143  * call; the followup may be _prepare(), which is idempotent and will
144  * return the same result; it may be _commit(), but only when the
145  * preceding _prepare() has reported success; or it may be _rollback(),
146  * regardless of the state reported by _prepare().
147  *
148  * Invalid sequences are reported through assert() -- which is not a
149  * bug but a facility!  It helps the plugin user to code transaction
150  * logic correctly.  The implicitness of transactions means that we
151  * cannot capture all logic failures though.
152  */
153
154 int pulleyback_prepare (void *pbh) {
155         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
156         int ok = 1;
157         switch (self->txn_state) {
158         case TXN_NONE:
159                 // We want to return success, so we'd better create an
160                 // empty transaction to permit future _commit() or _rollback()
161                 ok = ok && have_txn (self);
162                 // ...continue into case TXN_ACTIVE...
163         case TXN_ACTIVE:
164                 ok = ok && (0 == self->txn->prepare (self->txn, self->txn_gid));
165                 self->txn_state = ok? TXN_SUCCESS: TXN_ABORT;
166                 break;
167         case TXN_SUCCESS:
168                 // The transaction has already been successfully prepared
169                 ok = ok && 1;
170                 break;
171         case TXN_ABORT:
172                 // The transaction has already failed preparing for commit
173                 ok = 0;
174                 break;
175         }
176         return ok;
177 }
178
179 int pulleyback_commit (void *pbh) {
180         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
181         int ok = 1;
182         switch (self->txn_state) {
183         case TXN_NONE:
184                 // We can safely report success when there is no transaction
185                 ok = 1;
186                 break;
187         case TXN_ACTIVE:
188         case TXN_SUCCESS:
189                 // The transaction is in full progress; attempt to commit it
190                 ok = ok && (0 == self->txn->commit (self->txn, 0));
191                 self->txn = NULL;
192                 self->txn_state = TXN_NONE;
193                 break;
194         case TXN_ABORT:
195                 // Preparation fails, then the call should havae been _rollback()
196                 assert (self->txn_state != TXN_ABORT);
197                 ok = ok && 0;
198                 break;
199         }
200         return ok;
201 }
202
203 void pulleyback_rollback (void *pbh) {
204         struct pulleyback_tlspool *self = (struct pulleyback_tlspool *) pbh;
205         int ok = 1;
206         switch (self->txn_state) {
207         case TXN_NONE:
208                 // In lieu of a transaction, rollback is a trivial matter
209                 ok = ok && 1;
210                 break;
211         case TXN_ABORT:
212         case TXN_SUCCESS:
213                 // Preparation of the transaction has been done,
214                 // so process as we would an active transaction
215         case TXN_ACTIVE:
216                 // When there actually is a transaction, roll it back
217                 ok = ok && (0 == self->txn->abort (self->txn));
218                 self->txn = NULL;
219                 self->txn_state = TXN_NONE;
220                 break;
221         }
222 }
223
224 int pulleyback_collaborate (void *pbh1, void *pbh2) {
225         struct pulleyback_tlspool *data1 = (struct pulleyback_tlspool *) pbh1;
226         struct pulleyback_tlspool *data2 = (struct pulleyback_tlspool *) pbh2;
227         int ok = 1;
228         ok = ok && (0 == strcmp (data1->db_env, data2->db_env));
229         //TODO// May need to copy self->env and reopen self->db in it
230         if (!ok) {
231                 ;  // Do not continue 
232         } else if (data1->txn == NULL) {
233                 if (data2->txn == NULL) {
234                         // Neither has a transaction, so must create it
235                         ok = ok && have_txn (data2);
236                 }
237                 if (ok) {
238                         data1->txn = data2->txn;
239                 }
240         } else if (data2->txn == NULL) {
241                 data2->txn = data1->txn;
242         } else {
243                 ok = (data1->txn == data2->txn);
244         }
245         return ok;
246 }
247