Merge "chan_ooh323: fix h323 log file path"
[asterisk/asterisk.git] / res / res_odbc_transaction.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2016, Digium, Inc.
5  *
6  * Mark Michelson <mmichelson@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 #include "asterisk.h"
20
21 #include "asterisk/res_odbc.h"
22 #include "asterisk/res_odbc_transaction.h"
23 #include "asterisk/channel.h"
24 #include "asterisk/pbx.h"
25 #include "asterisk/app.h"
26 #include "asterisk/module.h"
27
28 /*** MODULEINFO
29         <depend>generic_odbc</depend>
30         <support_level>core</support_level>
31  ***/
32
33 /*** DOCUMENTATION
34         <function name="ODBC" language="en_US">
35                 <synopsis>
36                         Controls ODBC transaction properties.
37                 </synopsis>
38                 <syntax>
39                         <parameter name="property" required="true">
40                                 <enumlist>
41                                         <enum name="transaction">
42                                                 <para>Gets or sets the active transaction ID.  If set, and the transaction ID does not
43                                                 exist and a <replaceable>database name</replaceable> is specified as an argument, it will be created.</para>
44                                         </enum>
45                                         <enum name="forcecommit">
46                                                 <para>Controls whether a transaction will be automatically committed when the channel
47                                                 hangs up.  Defaults to false.  If a <replaceable>transaction ID</replaceable> is specified in the optional argument,
48                                                 the property will be applied to that ID, otherwise to the current active ID.</para>
49                                         </enum>
50                                         <enum name="isolation">
51                                                 <para>Controls the data isolation on uncommitted transactions.  May be one of the
52                                                 following: <literal>read_committed</literal>, <literal>read_uncommitted</literal>,
53                                                 <literal>repeatable_read</literal>, or <literal>serializable</literal>.  Defaults to the
54                                                 database setting in <filename>res_odbc.conf</filename> or <literal>read_committed</literal>
55                                                 if not specified.  If a <replaceable>transaction ID</replaceable> is specified as an optional argument, it will be
56                                                 applied to that ID, otherwise the current active ID.</para>
57                                         </enum>
58                                 </enumlist>
59                         </parameter>
60                         <parameter name="argument" required="false" />
61                 </syntax>
62                 <description>
63                         <para>The ODBC() function allows setting several properties to influence how a connected
64                         database processes transactions.</para>
65                 </description>
66         </function>
67         <application name="ODBC_Commit" language="en_US">
68                 <synopsis>
69                         Commits a currently open database transaction.
70                 </synopsis>
71                 <syntax>
72                         <parameter name="transaction ID" required="no" />
73                 </syntax>
74                 <description>
75                         <para>Commits the database transaction specified by <replaceable>transaction ID</replaceable>
76                         or the current active transaction, if not specified.</para>
77                 </description>
78         </application>
79         <application name="ODBC_Rollback" language="en_US">
80                 <synopsis>
81                         Rollback a currently open database transaction.
82                 </synopsis>
83                 <syntax>
84                         <parameter name="transaction ID" required="no" />
85                 </syntax>
86                 <description>
87                         <para>Rolls back the database transaction specified by <replaceable>transaction ID</replaceable>
88                         or the current active transaction, if not specified.</para>
89                 </description>
90         </application>
91  ***/
92
93 struct odbc_txn_frame {
94         AST_LIST_ENTRY(odbc_txn_frame) list;
95         struct odbc_obj *obj;        /*!< Database handle within which transacted statements are run */
96         /*!\brief Is this record the current active transaction within the channel?
97          * Note that the active flag is really only necessary for statements which
98          * are triggered from the dialplan, as there isn't a direct correlation
99          * between multiple statements.  Applications wishing to use transactions
100          * may simply perform each statement on the same odbc_obj, which keeps the
101          * transaction persistent.
102          */
103         unsigned int active:1;
104         unsigned int forcecommit:1;     /*!< Should uncommitted transactions be auto-committed on handle release? */
105         unsigned int isolation;         /*!< Flags for how the DB should deal with data in other, uncommitted transactions */
106         char name[0];                   /*!< Name of this transaction ID */
107 };
108
109 static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx);
110
111 static void odbc_txn_free(void *vdata)
112 {
113         struct odbc_txn_frame *tx;
114         AST_LIST_HEAD(, odbc_txn_frame) *oldlist = vdata;
115
116         ast_debug(2, "odbc_txn_free(%p) called\n", vdata);
117
118         AST_LIST_LOCK(oldlist);
119         while ((tx = AST_LIST_REMOVE_HEAD(oldlist, list))) {
120                 release_transaction(tx);
121         }
122         AST_LIST_UNLOCK(oldlist);
123         AST_LIST_HEAD_DESTROY(oldlist);
124         ast_free(oldlist);
125 }
126
127 static const struct ast_datastore_info txn_info = {
128         .type = "ODBC_Transaction",
129         .destroy = odbc_txn_free,
130 };
131
132 static struct odbc_txn_frame *create_transaction(struct ast_channel *chan, const char *name, const char *dsn)
133 {
134         struct ast_datastore *txn_store;
135         AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
136         struct odbc_txn_frame *txn = NULL;
137         struct odbc_txn_frame *otxn;
138
139         if (ast_strlen_zero(dsn)) {
140                 return NULL;
141         }
142
143         ast_channel_lock(chan);
144         if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
145                 oldlist = txn_store->data;
146         } else {
147                 if (!(txn_store = ast_datastore_alloc(&txn_info, NULL))) {
148                         ast_log(LOG_ERROR, "Unable to allocate a new datastore.  Cannot create a new transaction.\n");
149                         ast_channel_unlock(chan);
150                         return NULL;
151                 }
152
153                 if (!(oldlist = ast_calloc(1, sizeof(*oldlist)))) {
154                         ast_log(LOG_ERROR, "Unable to allocate datastore list head.  Cannot create a new transaction.\n");
155                         ast_datastore_free(txn_store);
156                         ast_channel_unlock(chan);
157                         return NULL;
158                 }
159
160                 txn_store->data = oldlist;
161                 AST_LIST_HEAD_INIT(oldlist);
162                 ast_channel_datastore_add(chan, txn_store);
163         }
164         ast_channel_unlock(chan);
165
166         txn = ast_calloc(1, sizeof(*txn) + strlen(name) + 1);
167         if (!txn) {
168                 return NULL;
169         }
170
171         strcpy(txn->name, name); /* SAFE */
172         txn->obj = ast_odbc_request_obj(dsn, 0);
173         if (!txn->obj) {
174                 ast_free(txn);
175                 return NULL;
176         }
177         txn->isolation = ast_odbc_class_get_isolation(txn->obj->parent);
178         txn->forcecommit = ast_odbc_class_get_isolation(txn->obj->parent);
179         txn->active = 1;
180
181         if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_OFF, 0) == SQL_ERROR) {
182                 ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr (Autocommit)");
183                 ast_odbc_release_obj(txn->obj);
184                 ast_free(txn);
185                 return NULL;
186         }
187
188         /* Set the isolation property */
189         if (SQLSetConnectAttr(txn->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)txn->isolation, 0) == SQL_ERROR) {
190                 ast_odbc_print_errors(SQL_HANDLE_DBC, txn->obj->con, "SetConnectAttr");
191                 ast_odbc_release_obj(txn->obj);
192                 ast_free(txn);
193                 return NULL;
194         }
195
196         /* On creation, the txn becomes active, and all others inactive */
197         AST_LIST_LOCK(oldlist);
198         AST_LIST_TRAVERSE(oldlist, otxn, list) {
199                 otxn->active = 0;
200         }
201         AST_LIST_INSERT_TAIL(oldlist, txn, list);
202         AST_LIST_UNLOCK(oldlist);
203
204         return txn;
205 }
206
207 static struct odbc_txn_frame *find_transaction(struct ast_channel *chan, const char *name, int active)
208 {
209         struct ast_datastore *txn_store;
210         AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
211         struct odbc_txn_frame *txn = NULL;
212
213         if (!chan || (!active && !name)) {
214                 return NULL;
215         }
216
217         ast_channel_lock(chan);
218         txn_store = ast_channel_datastore_find(chan, &txn_info, NULL);
219         ast_channel_unlock(chan);
220
221         if (!txn_store) {
222                 /* No datastore? Definitely no transaction then */
223                 return NULL;
224         }
225
226         oldlist = txn_store->data;
227         AST_LIST_LOCK(oldlist);
228
229         AST_LIST_TRAVERSE(oldlist, txn, list) {
230                 if (active) {
231                         if (txn->active) {
232                                 break;
233                         }
234                 } else if (!strcasecmp(txn->name, name)) {
235                         break;
236                 }
237         }
238         AST_LIST_UNLOCK(oldlist);
239
240         return txn;
241 }
242
243 static struct odbc_txn_frame *release_transaction(struct odbc_txn_frame *tx)
244 {
245         if (!tx) {
246                 return NULL;
247         }
248
249         ast_debug(2, "release_transaction(%p) called (tx->obj = %p\n", tx, tx->obj);
250
251         ast_debug(1, "called on a transactional handle with %s\n", tx->forcecommit ? "COMMIT" : "ROLLBACK");
252         if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, tx->forcecommit ? SQL_COMMIT : SQL_ROLLBACK) == SQL_ERROR) {
253                 ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
254         }
255
256         /* Transaction is done, reset autocommit
257          *
258          * XXX I'm unsure if this is actually necessary, since we're releasing
259          * the connection back to unixODBC. However, if unixODBC pooling is enabled,
260          * it can't hurt to do just in case.
261          */
262         if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_AUTOCOMMIT, (void *)SQL_AUTOCOMMIT_ON, 0) == SQL_ERROR) {
263                 ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLSetAttr");
264         }
265
266         ast_odbc_release_obj(tx->obj);
267         ast_free(tx);
268         return NULL;
269 }
270
271 static int commit_exec(struct ast_channel *chan, const char *data)
272 {
273         struct odbc_txn_frame *tx;
274
275         if (ast_strlen_zero(data)) {
276                 tx = find_transaction(chan, NULL, 1);
277         } else {
278                 tx = find_transaction(chan, data, 0);
279         }
280
281         /* XXX COMMIT_RESULT is set to OK even if no transaction was found. Very misleading */
282         pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", "OK");
283
284         if (tx) {
285                 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_COMMIT) == SQL_ERROR) {
286                         struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
287                         pbx_builtin_setvar_helper(chan, "COMMIT_RESULT", ast_str_buffer(errors));
288                 }
289         }
290         return 0;
291 }
292
293 static int rollback_exec(struct ast_channel *chan, const char *data)
294 {
295         struct odbc_txn_frame *tx;
296
297         if (ast_strlen_zero(data)) {
298                 tx = find_transaction(chan, NULL, 1);
299         } else {
300                 tx = find_transaction(chan, data, 0);
301         }
302
303         /* XXX ROLLBACK_RESULT is set to OK even if no transaction was found. Very misleading */
304         pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", "OK");
305
306         if (tx) {
307                 if (SQLEndTran(SQL_HANDLE_DBC, tx->obj->con, SQL_ROLLBACK) == SQL_ERROR) {
308                         struct ast_str *errors = ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SQLEndTran");
309                         pbx_builtin_setvar_helper(chan, "ROLLBACK_RESULT", ast_str_buffer(errors));
310                 }
311         }
312         return 0;
313 }
314
315 static int acf_transaction_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
316 {
317         AST_DECLARE_APP_ARGS(args,
318                 AST_APP_ARG(property);
319                 AST_APP_ARG(opt);
320         );
321         struct odbc_txn_frame *tx;
322
323         AST_STANDARD_APP_ARGS(args, data);
324         if (strcasecmp(args.property, "transaction") == 0) {
325                 if ((tx = find_transaction(chan, NULL, 1))) {
326                         ast_copy_string(buf, tx->name, len);
327                         return 0;
328                 }
329         } else if (strcasecmp(args.property, "isolation") == 0) {
330                 if (!ast_strlen_zero(args.opt)) {
331                         tx = find_transaction(chan, args.opt, 0);
332                 } else {
333                         tx = find_transaction(chan, NULL, 1);
334                 }
335                 if (tx) {
336                         ast_copy_string(buf, ast_odbc_isolation2text(tx->isolation), len);
337                         return 0;
338                 }
339         } else if (strcasecmp(args.property, "forcecommit") == 0) {
340                 if (!ast_strlen_zero(args.opt)) {
341                         tx = find_transaction(chan, args.opt, 0);
342                 } else {
343                         tx = find_transaction(chan, NULL, 1);
344                 }
345                 if (tx) {
346                         ast_copy_string(buf, tx->forcecommit ? "1" : "0", len);
347                         return 0;
348                 }
349         }
350         return -1;
351 }
352
353 /* XXX The idea of "active" transactions is silly and makes things
354  * more prone to error. It would be much better if the transaction
355  * always had to be specified by name so that no implicit behavior
356  * occurred.
357  */
358 static int mark_transaction_active(struct ast_channel *chan, struct odbc_txn_frame *tx)
359 {
360         struct ast_datastore *txn_store;
361         AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
362         struct odbc_txn_frame *active = NULL, *txn;
363
364         if (!chan) {
365                 return -1;
366         }
367
368         ast_channel_lock(chan);
369         if (!(txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
370                 ast_channel_unlock(chan);
371                 return -1;
372         }
373
374         oldlist = txn_store->data;
375         AST_LIST_LOCK(oldlist);
376         AST_LIST_TRAVERSE(oldlist, txn, list) {
377                 if (txn == tx) {
378                         txn->active = 1;
379                         active = txn;
380                 } else {
381                         txn->active = 0;
382                 }
383         }
384         AST_LIST_UNLOCK(oldlist);
385         ast_channel_unlock(chan);
386         return active ? 0 : -1;
387 }
388
389 static int acf_transaction_write(struct ast_channel *chan, const char *cmd, char *s, const char *value)
390 {
391         AST_DECLARE_APP_ARGS(args,
392                 AST_APP_ARG(property);
393                 AST_APP_ARG(opt);
394         );
395         struct odbc_txn_frame *tx;
396
397         AST_STANDARD_APP_ARGS(args, s);
398         if (strcasecmp(args.property, "transaction") == 0) {
399                 /* Set active transaction */
400                 if ((tx = find_transaction(chan, value, 0))) {
401                         mark_transaction_active(chan, tx);
402                 } else if (!create_transaction(chan, value, args.opt)) {
403                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
404                         return -1;
405                 }
406                 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
407                 return 0;
408         } else if (strcasecmp(args.property, "forcecommit") == 0) {
409                 /* Set what happens when an uncommitted transaction ends without explicit Commit or Rollback */
410                 if (ast_strlen_zero(args.opt)) {
411                         tx = find_transaction(chan, NULL, 1);
412                 } else {
413                         tx = find_transaction(chan, args.opt, 0);
414                 }
415                 if (!tx) {
416                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
417                         return -1;
418                 }
419                 if (ast_true(value)) {
420                         tx->forcecommit = 1;
421                 } else if (ast_false(value)) {
422                         tx->forcecommit = 0;
423                 } else {
424                         ast_log(LOG_ERROR, "Invalid value for forcecommit: '%s'\n", S_OR(value, ""));
425                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
426                         return -1;
427                 }
428
429                 pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
430                 return 0;
431         } else if (strcasecmp(args.property, "isolation") == 0) {
432                 /* How do uncommitted transactions affect reads? */
433                 /* XXX This is completely useless. The problem is that setting the isolation here
434                  * does not actually alter the connection. The only time the isolation gets set is
435                  * when the transaction is created. The only way to set isolation is to set it on
436                  * the ODBC class's configuration in res_odbc.conf.
437                  */
438                 int isolation = ast_odbc_text2isolation(value);
439                 if (ast_strlen_zero(args.opt)) {
440                         tx = find_transaction(chan, NULL, 1);
441                 } else {
442                         tx = find_transaction(chan, args.opt, 0);
443                 }
444                 if (!tx) {
445                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "FAILED_TO_CREATE");
446                         return -1;
447                 }
448                 if (isolation == 0) {
449                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "INVALID_VALUE");
450                         ast_log(LOG_ERROR, "Invalid isolation specification: '%s'\n", S_OR(value, ""));
451                 } else if (SQLSetConnectAttr(tx->obj->con, SQL_ATTR_TXN_ISOLATION, (void *)(long)isolation, 0) == SQL_ERROR) {
452                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "SQL_ERROR");
453                         ast_odbc_print_errors(SQL_HANDLE_DBC, tx->obj->con, "SetConnectAttr (Txn isolation)");
454                 } else {
455                         pbx_builtin_setvar_helper(chan, "ODBC_RESULT", "OK");
456                         tx->isolation = isolation;
457                 }
458                 return 0;
459         } else {
460                 ast_log(LOG_ERROR, "Unknown property: '%s'\n", args.property);
461                 return -1;
462         }
463 }
464
465 struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname)
466 {
467         struct ast_datastore *txn_store;
468         AST_LIST_HEAD(, odbc_txn_frame) *oldlist;
469         struct odbc_txn_frame *txn = NULL;
470
471         if (!chan || !objname) {
472                 /* No channel == no transaction */
473                 return NULL;
474         }
475
476         ast_channel_lock(chan);
477         if ((txn_store = ast_channel_datastore_find(chan, &txn_info, NULL))) {
478                 oldlist = txn_store->data;
479         } else {
480                 ast_channel_unlock(chan);
481                 return NULL;
482         }
483
484         AST_LIST_LOCK(oldlist);
485         ast_channel_unlock(chan);
486
487         AST_LIST_TRAVERSE(oldlist, txn, list) {
488                 if (txn->obj && txn->obj->parent && !strcmp(ast_odbc_class_get_name(txn->obj->parent), objname)) {
489                         AST_LIST_UNLOCK(oldlist);
490                         return txn->obj;
491                 }
492         }
493         AST_LIST_UNLOCK(oldlist);
494         return NULL;
495 }
496
497 static struct ast_custom_function odbc_function = {
498         .name = "ODBC",
499         .read = acf_transaction_read,
500         .write = acf_transaction_write,
501 };
502
503 static const char * const app_commit = "ODBC_Commit";
504 static const char * const app_rollback = "ODBC_Rollback";
505
506 /* XXX res_odbc takes the path of disallowing unloads from happening.
507  * It's not a great precedent, but since trying to deal with unloading the module
508  * while transactions are active seems like a huge pain to deal with, we'll go
509  * the same way here.
510  */
511 static int unload_module(void)
512 {
513         return -1;
514 }
515
516 static int load_module(void)
517 {
518         ast_register_application_xml(app_commit, commit_exec);
519         ast_register_application_xml(app_rollback, rollback_exec);
520         ast_custom_function_register(&odbc_function);
521         return 0;
522 }
523
524 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "ODBC transaction resource",
525         .support_level = AST_MODULE_SUPPORT_CORE,
526         .load = load_module,
527         .unload = unload_module,
528         .load_pri = AST_MODPRI_REALTIME_DEPEND,
529 );