res_odbc_transaction.c 16 KB

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