res_config_mysql.c 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 1999-2005, Digium, Inc.
  5. *
  6. * Mark Spencer <markster@digium.com> - Asterisk Author
  7. * Matthew Boehm <mboehm@cytelcom.com> - MySQL RealTime Driver Author
  8. *
  9. * See http://www.asterisk.org for more information about
  10. * the Asterisk project. Please do not directly contact
  11. * any of the maintainers of this project for assistance;
  12. * the project provides a web site, mailing lists and IRC
  13. * channels for your use.
  14. *
  15. * This program is free software, distributed under the terms of
  16. * the GNU General Public License Version 2. See the LICENSE file
  17. * at the top of the source tree.
  18. */
  19. /*!
  20. * \file
  21. * \brief MySQL CDR backend
  22. */
  23. /*** MODULEINFO
  24. <depend>mysqlclient</depend>
  25. <defaultenabled>no</defaultenabled>
  26. <support_level>extended</support_level>
  27. ***/
  28. #include "asterisk.h"
  29. #include <sys/stat.h>
  30. #include <mysql/mysql.h>
  31. #include <mysql/errmsg.h>
  32. #include "asterisk/channel.h"
  33. #include "asterisk/logger.h"
  34. #include "asterisk/config.h"
  35. #include "asterisk/module.h"
  36. #include "asterisk/lock.h"
  37. #include "asterisk/options.h"
  38. #include "asterisk/cli.h"
  39. #include "asterisk/utils.h"
  40. #include "asterisk/threadstorage.h"
  41. #include "asterisk/strings.h"
  42. #define RES_CONFIG_MYSQL_CONF "res_config_mysql.conf"
  43. #define RES_CONFIG_MYSQL_CONF_OLD "res_mysql.conf"
  44. #define READHANDLE 0
  45. #define WRITEHANDLE 1
  46. #define ESCAPE_STRING(buf, var) \
  47. do { \
  48. struct ast_str *semi = ast_str_thread_get(&scratch2_buf, strlen(var) * 3 + 1); \
  49. const char *chunk = var; \
  50. ast_str_reset(semi); \
  51. for (; *chunk; chunk++) { \
  52. if (strchr(";^", *chunk)) { \
  53. ast_str_append(&semi, 0, "^%02hhX", *chunk); \
  54. } else { \
  55. ast_str_append(&semi, 0, "%c", *chunk); \
  56. } \
  57. } \
  58. if (ast_str_strlen(semi) * 2 + 1 > ast_str_size(buf)) { \
  59. ast_str_make_space(&(buf), ast_str_strlen(semi) * 2 + 1); \
  60. } \
  61. mysql_real_escape_string(&dbh->handle, ast_str_buffer(buf), ast_str_buffer(semi), ast_str_strlen(semi)); \
  62. } while (0)
  63. AST_THREADSTORAGE(sql_buf);
  64. AST_THREADSTORAGE(sql2_buf);
  65. AST_THREADSTORAGE(find_buf);
  66. AST_THREADSTORAGE(scratch_buf);
  67. AST_THREADSTORAGE(scratch2_buf);
  68. AST_THREADSTORAGE(modify_buf);
  69. AST_THREADSTORAGE(modify2_buf);
  70. AST_THREADSTORAGE(modify3_buf);
  71. enum requirements { RQ_WARN, RQ_CREATECLOSE, RQ_CREATECHAR };
  72. struct mysql_conn {
  73. AST_RWLIST_ENTRY(mysql_conn) list;
  74. ast_mutex_t lock;
  75. MYSQL handle;
  76. char host[MAXHOSTNAMELEN];
  77. char name[50];
  78. char user[50];
  79. char pass[50];
  80. char sock[50];
  81. char charset[50];
  82. int port;
  83. int connected;
  84. time_t connect_time;
  85. enum requirements requirements;
  86. char unique_name[0];
  87. };
  88. struct columns {
  89. char *name;
  90. char *type;
  91. char *dflt;
  92. char null;
  93. int len;
  94. AST_LIST_ENTRY(columns) list;
  95. };
  96. struct tables {
  97. ast_mutex_t lock;
  98. AST_LIST_HEAD_NOLOCK(mysql_columns, columns) columns;
  99. AST_LIST_ENTRY(tables) list;
  100. struct mysql_conn *database;
  101. char name[0];
  102. };
  103. static AST_LIST_HEAD_STATIC(mysql_tables, tables);
  104. static AST_RWLIST_HEAD_STATIC(databases, mysql_conn);
  105. static int parse_config(int reload);
  106. static int mysql_reconnect(struct mysql_conn *conn);
  107. static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  108. static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
  109. static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn);
  110. static int require_mysql(const char *database, const char *tablename, va_list ap);
  111. static struct ast_cli_entry cli_realtime_mysql_status[] = {
  112. AST_CLI_DEFINE(handle_cli_realtime_mysql_status, "Shows connection information for the MySQL RealTime driver"),
  113. AST_CLI_DEFINE(handle_cli_realtime_mysql_cache, "Shows cached tables within the MySQL realtime driver"),
  114. };
  115. static struct mysql_conn *find_database(const char *database, int for_write)
  116. {
  117. char *whichdb;
  118. const char *ptr;
  119. struct mysql_conn *cur;
  120. if ((ptr = strchr(database, '/'))) {
  121. /* Multiple databases encoded within string */
  122. if (for_write) {
  123. whichdb = ast_strdupa(ptr + 1);
  124. } else {
  125. whichdb = ast_alloca(ptr - database + 1);
  126. strncpy(whichdb, database, ptr - database);
  127. whichdb[ptr - database] = '\0';
  128. }
  129. } else {
  130. whichdb = ast_strdupa(database);
  131. }
  132. AST_RWLIST_RDLOCK(&databases);
  133. AST_RWLIST_TRAVERSE(&databases, cur, list) {
  134. if (!strcmp(cur->unique_name, whichdb)) {
  135. ast_mutex_lock(&cur->lock);
  136. break;
  137. }
  138. }
  139. AST_RWLIST_UNLOCK(&databases);
  140. return cur;
  141. }
  142. #define release_database(a) ast_mutex_unlock(&(a)->lock)
  143. static void destroy_table(struct tables *table)
  144. {
  145. struct columns *column;
  146. ast_mutex_lock(&table->lock);
  147. while ((column = AST_LIST_REMOVE_HEAD(&table->columns, list))) {
  148. ast_free(column);
  149. }
  150. ast_mutex_unlock(&table->lock);
  151. ast_mutex_destroy(&table->lock);
  152. ast_free(table);
  153. }
  154. static struct tables *find_table(const char *database, const char *tablename)
  155. {
  156. struct columns *column;
  157. struct tables *table;
  158. struct ast_str *sql = ast_str_thread_get(&find_buf, 30);
  159. char *fname, *ftype, *flen, *fdflt, *fnull;
  160. struct mysql_conn *dbh;
  161. MYSQL_RES *result;
  162. MYSQL_ROW row;
  163. if (!(dbh = find_database(database, 1))) {
  164. return NULL;
  165. }
  166. AST_LIST_LOCK(&mysql_tables);
  167. AST_LIST_TRAVERSE(&mysql_tables, table, list) {
  168. if (!strcasecmp(table->name, tablename)) {
  169. ast_mutex_lock(&table->lock);
  170. AST_LIST_UNLOCK(&mysql_tables);
  171. release_database(dbh);
  172. return table;
  173. }
  174. }
  175. /* Not found, scan the table */
  176. ast_str_set(&sql, 0, "DESC %s", tablename);
  177. if (!mysql_reconnect(dbh)) {
  178. release_database(dbh);
  179. AST_LIST_UNLOCK(&mysql_tables);
  180. return NULL;
  181. }
  182. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  183. ast_log(LOG_ERROR, "Failed to query database '%s', table '%s' columns: %s\n", database, tablename, mysql_error(&dbh->handle));
  184. release_database(dbh);
  185. AST_LIST_UNLOCK(&mysql_tables);
  186. return NULL;
  187. }
  188. if (!(table = ast_calloc(1, sizeof(*table) + strlen(tablename) + 1))) {
  189. ast_log(LOG_ERROR, "Unable to allocate memory for new table structure\n");
  190. release_database(dbh);
  191. AST_LIST_UNLOCK(&mysql_tables);
  192. return NULL;
  193. }
  194. strcpy(table->name, tablename); /* SAFE */
  195. table->database = dbh;
  196. ast_mutex_init(&table->lock);
  197. AST_LIST_HEAD_INIT_NOLOCK(&table->columns);
  198. if ((result = mysql_store_result(&dbh->handle))) {
  199. while ((row = mysql_fetch_row(result))) {
  200. fname = row[0];
  201. ftype = row[1];
  202. fnull = row[2];
  203. fdflt = row[4];
  204. ast_verb(4, "Found column '%s' of type '%s'\n", fname, ftype);
  205. if (fdflt == NULL) {
  206. fdflt = "";
  207. }
  208. if (!(column = ast_calloc(1, sizeof(*column) + strlen(fname) + strlen(ftype) + strlen(fdflt) + 3))) {
  209. ast_log(LOG_ERROR, "Unable to allocate column element %s for %s\n", fname, tablename);
  210. destroy_table(table);
  211. release_database(dbh);
  212. AST_LIST_UNLOCK(&mysql_tables);
  213. return NULL;
  214. }
  215. if ((flen = strchr(ftype, '('))) {
  216. sscanf(flen, "(%30d)", &column->len);
  217. } else {
  218. /* Columns like dates, times, and timestamps don't have a length */
  219. column->len = -1;
  220. }
  221. column->name = (char *)column + sizeof(*column);
  222. column->type = (char *)column + sizeof(*column) + strlen(fname) + 1;
  223. column->dflt = (char *)column + sizeof(*column) + strlen(fname) + 1 + strlen(ftype) + 1;
  224. strcpy(column->name, fname);
  225. strcpy(column->type, ftype);
  226. strcpy(column->dflt, fdflt);
  227. column->null = (strcmp(fnull, "YES") == 0 ? 1 : 0);
  228. AST_LIST_INSERT_TAIL(&table->columns, column, list);
  229. }
  230. mysql_free_result(result);
  231. }
  232. AST_LIST_INSERT_TAIL(&mysql_tables, table, list);
  233. ast_mutex_lock(&table->lock);
  234. AST_LIST_UNLOCK(&mysql_tables);
  235. release_database(dbh);
  236. return table;
  237. }
  238. static void release_table(struct tables *table)
  239. {
  240. if (table) {
  241. ast_mutex_unlock(&table->lock);
  242. }
  243. }
  244. static struct columns *find_column(struct tables *table, const char *colname)
  245. {
  246. struct columns *column;
  247. AST_LIST_TRAVERSE(&table->columns, column, list) {
  248. if (strcmp(column->name, colname) == 0) {
  249. break;
  250. }
  251. }
  252. return column;
  253. }
  254. static char *decode_chunk(char *chunk)
  255. {
  256. char *orig = chunk;
  257. for (; *chunk; chunk++) {
  258. if (*chunk == '^' && strchr("0123456789ABCDEFabcdef", chunk[1]) && strchr("0123456789ABCDEFabcdef", chunk[2])) {
  259. sscanf(chunk + 1, "%02hhX", chunk);
  260. memmove(chunk + 1, chunk + 3, strlen(chunk + 3) + 1);
  261. }
  262. }
  263. return orig;
  264. }
  265. #define IS_SQL_LIKE_CLAUSE(x) ((x) && ast_ends_with(x, " LIKE"))
  266. /* MySQL requires us to escape the escape... yo dawg */
  267. static char *ESCAPE_CLAUSE = " ESCAPE '\\\\'";
  268. static struct ast_variable *realtime_mysql(const char *database, const char *table, const struct ast_variable *rt_fields)
  269. {
  270. struct mysql_conn *dbh;
  271. MYSQL_RES *result;
  272. MYSQL_ROW row;
  273. MYSQL_FIELD *fields;
  274. int numFields, i;
  275. struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
  276. struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
  277. char *stringp;
  278. char *chunk;
  279. char *op;
  280. char *escape = "";
  281. const struct ast_variable *field = rt_fields;
  282. struct ast_variable *var=NULL, *prev=NULL;
  283. if (!(dbh = find_database(database, 0))) {
  284. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: %s (check res_mysql.conf)\n", database);
  285. return NULL;
  286. }
  287. if (!table) {
  288. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  289. release_database(dbh);
  290. return NULL;
  291. }
  292. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  293. if (!field) {
  294. ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
  295. release_database(dbh);
  296. return NULL;
  297. }
  298. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  299. if (!mysql_reconnect(dbh)) {
  300. release_database(dbh);
  301. return NULL;
  302. }
  303. /* Create the first part of the query using the first parameter/value pairs we just extracted
  304. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  305. if (!strchr(field->name, ' ')) {
  306. op = " =";
  307. } else {
  308. op = "";
  309. if (IS_SQL_LIKE_CLAUSE(field->name)) {
  310. escape = ESCAPE_CLAUSE;
  311. }
  312. }
  313. ESCAPE_STRING(buf, field->value);
  314. ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", table, field->name, op, ast_str_buffer(buf), escape);
  315. while ((field = field->next)) {
  316. escape = "";
  317. if (!strchr(field->name, ' ')) {
  318. op = " =";
  319. } else {
  320. op = "";
  321. if (IS_SQL_LIKE_CLAUSE(field->name)) {
  322. escape = ESCAPE_CLAUSE;
  323. }
  324. }
  325. ESCAPE_STRING(buf, field->value);
  326. ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(buf), escape);
  327. }
  328. ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
  329. /* Execution. */
  330. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  331. ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
  332. release_database(dbh);
  333. return NULL;
  334. }
  335. if ((result = mysql_store_result(&dbh->handle))) {
  336. numFields = mysql_num_fields(result);
  337. fields = mysql_fetch_fields(result);
  338. while ((row = mysql_fetch_row(result))) {
  339. for (i = 0; i < numFields; i++) {
  340. /* Encode NULL values separately from blank values, for the Realtime API */
  341. if (row[i] == NULL) {
  342. row[i] = "";
  343. } else if (ast_strlen_zero(row[i])) {
  344. row[i] = " ";
  345. }
  346. for (stringp = row[i], chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
  347. if (prev) {
  348. if ((prev->next = ast_variable_new(fields[i].name, decode_chunk(chunk), ""))) {
  349. prev = prev->next;
  350. }
  351. } else {
  352. prev = var = ast_variable_new(fields[i].name, decode_chunk(chunk), "");
  353. }
  354. }
  355. }
  356. }
  357. } else {
  358. ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
  359. }
  360. release_database(dbh);
  361. mysql_free_result(result);
  362. return var;
  363. }
  364. static struct ast_config *realtime_multi_mysql(const char *database, const char *table, const struct ast_variable *rt_fields)
  365. {
  366. struct mysql_conn *dbh;
  367. MYSQL_RES *result;
  368. MYSQL_ROW row;
  369. MYSQL_FIELD *fields;
  370. int numFields, i;
  371. struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
  372. struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
  373. const char *initfield = NULL;
  374. char *stringp;
  375. char *chunk;
  376. char *op;
  377. char *escape = "";
  378. const struct ast_variable *field = rt_fields;
  379. struct ast_variable *var = NULL;
  380. struct ast_config *cfg = NULL;
  381. struct ast_category *cat = NULL;
  382. if (!(dbh = find_database(database, 0))) {
  383. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
  384. return NULL;
  385. }
  386. if (!table) {
  387. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  388. release_database(dbh);
  389. return NULL;
  390. }
  391. if (!(cfg = ast_config_new())) {
  392. /* If I can't alloc memory at this point, why bother doing anything else? */
  393. ast_log(LOG_WARNING, "Out of memory!\n");
  394. release_database(dbh);
  395. return NULL;
  396. }
  397. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  398. if (!field) {
  399. ast_log(LOG_WARNING, "MySQL RealTime: Realtime retrieval requires at least 1 parameter and 1 value to search on.\n");
  400. ast_config_destroy(cfg);
  401. release_database(dbh);
  402. return NULL;
  403. }
  404. initfield = ast_strdupa(field->name);
  405. if ((op = strchr(initfield, ' '))) {
  406. *op = '\0';
  407. }
  408. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  409. if (!mysql_reconnect(dbh)) {
  410. release_database(dbh);
  411. ast_config_destroy(cfg);
  412. return NULL;
  413. }
  414. /* Create the first part of the query using the first parameter/value pairs we just extracted
  415. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  416. if (!strchr(field->name, ' ')) {
  417. op = " =";
  418. } else {
  419. op = "";
  420. if (IS_SQL_LIKE_CLAUSE(field->name)) {
  421. escape = ESCAPE_CLAUSE;
  422. }
  423. }
  424. ESCAPE_STRING(buf, field->value);
  425. ast_str_set(&sql, 0, "SELECT * FROM %s WHERE %s%s '%s'%s", table, field->name, op, ast_str_buffer(buf), escape);
  426. while ((field = field->next)) {
  427. escape = "";
  428. if (!strchr(field->name, ' ')) {
  429. op = " =";
  430. } else {
  431. op = "";
  432. if (IS_SQL_LIKE_CLAUSE(field->name)) {
  433. escape = ESCAPE_CLAUSE;
  434. }
  435. }
  436. ESCAPE_STRING(buf, field->value);
  437. ast_str_append(&sql, 0, " AND %s%s '%s'%s", field->name, op, ast_str_buffer(buf), escape);
  438. }
  439. if (initfield) {
  440. ast_str_append(&sql, 0, " ORDER BY %s", initfield);
  441. }
  442. ast_debug(1, "MySQL RealTime: Retrieve SQL: %s\n", ast_str_buffer(sql));
  443. /* Execution. */
  444. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  445. ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database: %s\n", mysql_error(&dbh->handle));
  446. release_database(dbh);
  447. ast_config_destroy(cfg);
  448. return NULL;
  449. }
  450. if ((result = mysql_store_result(&dbh->handle))) {
  451. numFields = mysql_num_fields(result);
  452. fields = mysql_fetch_fields(result);
  453. while ((row = mysql_fetch_row(result))) {
  454. var = NULL;
  455. cat = ast_category_new_anonymous();
  456. if (!cat) {
  457. continue;
  458. }
  459. for (i = 0; i < numFields; i++) {
  460. if (ast_strlen_zero(row[i]))
  461. continue;
  462. for (stringp = row[i], chunk = strsep(&stringp, ";"); chunk; chunk = strsep(&stringp, ";")) {
  463. if (chunk && !ast_strlen_zero(decode_chunk(ast_strip(chunk)))) {
  464. if (initfield && !strcmp(initfield, fields[i].name)) {
  465. ast_category_rename(cat, chunk);
  466. }
  467. var = ast_variable_new(fields[i].name, chunk, "");
  468. ast_variable_append(cat, var);
  469. }
  470. }
  471. }
  472. ast_category_append(cfg, cat);
  473. }
  474. } else {
  475. ast_debug(1, "MySQL RealTime: Could not find any rows in table %s.\n", table);
  476. }
  477. release_database(dbh);
  478. mysql_free_result(result);
  479. return cfg;
  480. }
  481. static int update_mysql(const char *database, const char *tablename, const char *keyfield, const char *lookup, const struct ast_variable *rt_fields)
  482. {
  483. struct mysql_conn *dbh;
  484. uint64_t numrows;
  485. const struct ast_variable *field = rt_fields;
  486. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
  487. struct tables *table;
  488. struct columns *column = NULL;
  489. if (!(dbh = find_database(database, 1))) {
  490. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
  491. return -1;
  492. }
  493. if (!tablename) {
  494. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  495. release_database(dbh);
  496. return -1;
  497. }
  498. if (!(table = find_table(database, tablename))) {
  499. ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
  500. release_database(dbh);
  501. return -1;
  502. }
  503. if (!(column = find_column(table, keyfield))) {
  504. ast_log(LOG_ERROR, "MySQL RealTime: Updating on column '%s', but that column does not exist within the table '%s' (db '%s')!\n", keyfield, tablename, database);
  505. release_table(table);
  506. release_database(dbh);
  507. return -1;
  508. }
  509. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  510. if (!field) {
  511. ast_log(LOG_WARNING, "MySQL RealTime: Realtime update requires at least 1 parameter and 1 value to update.\n");
  512. release_table(table);
  513. release_database(dbh);
  514. return -1;
  515. }
  516. /* Check that the column exists in the table */
  517. if (!(column = find_column(table, field->name))) {
  518. ast_log(LOG_ERROR, "MySQL RealTime: Updating column '%s', but that column does not exist within the table '%s' (first pair MUST exist)!\n", field->name, tablename);
  519. release_table(table);
  520. release_database(dbh);
  521. return -1;
  522. }
  523. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  524. if (!mysql_reconnect(dbh)) {
  525. release_table(table);
  526. release_database(dbh);
  527. return -1;
  528. }
  529. /* Create the first part of the query using the first parameter/value pairs we just extracted
  530. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  531. ESCAPE_STRING(buf, field->value);
  532. ast_str_set(&sql, 0, "UPDATE %s SET `%s` = '%s'", tablename, field->name, ast_str_buffer(buf));
  533. while ((field = field->next)) {
  534. /* If the column is not within the table, then skip it */
  535. if (!(column = find_column(table, field->name))) {
  536. ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename);
  537. continue;
  538. }
  539. ESCAPE_STRING(buf, field->value);
  540. ast_str_append(&sql, 0, ", `%s` = '%s'", field->name, ast_str_buffer(buf));
  541. }
  542. ESCAPE_STRING(buf, lookup);
  543. ast_str_append(&sql, 0, " WHERE `%s` = '%s'", keyfield, ast_str_buffer(buf));
  544. ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
  545. /* Execution. */
  546. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  547. ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
  548. release_table(table);
  549. release_database(dbh);
  550. return -1;
  551. }
  552. numrows = mysql_affected_rows(&dbh->handle);
  553. release_table(table);
  554. release_database(dbh);
  555. ast_debug(1, "MySQL RealTime: Updated %" PRIu64 " rows on table: %s\n", numrows, tablename);
  556. /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
  557. * An integer greater than zero indicates the number of rows affected
  558. * Zero indicates that no records were updated
  559. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  560. */
  561. return (int)numrows;
  562. }
  563. static int update2_mysql(const char *database, const char *tablename, const struct ast_variable *lookup_fields, const struct ast_variable *update_fields)
  564. {
  565. struct mysql_conn *dbh;
  566. uint64_t numrows;
  567. int first;
  568. const struct ast_variable *field;
  569. struct ast_str *sql = ast_str_thread_get(&sql_buf, 100), *buf = ast_str_thread_get(&scratch_buf, 100);
  570. struct ast_str *where = ast_str_thread_get(&sql2_buf, 100);
  571. struct tables *table;
  572. struct columns *column = NULL;
  573. if (!tablename) {
  574. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  575. return -1;
  576. }
  577. if (!(dbh = find_database(database, 1))) {
  578. ast_log(LOG_ERROR, "Invalid database specified: %s\n", database);
  579. return -1;
  580. }
  581. if (!(table = find_table(database, tablename))) {
  582. ast_log(LOG_ERROR, "Table '%s' does not exist!!\n", tablename);
  583. release_database(dbh);
  584. return -1;
  585. }
  586. if (!sql || !buf || !where) {
  587. release_database(dbh);
  588. release_table(table);
  589. return -1;
  590. }
  591. ast_str_set(&sql, 0, "UPDATE %s SET", tablename);
  592. ast_str_set(&where, 0, "WHERE");
  593. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  594. if (!mysql_reconnect(dbh)) {
  595. release_table(table);
  596. release_database(dbh);
  597. return -1;
  598. }
  599. first = 1;
  600. for (field = lookup_fields; field; field = field->next) {
  601. if (!(column = find_column(table, field->name))) {
  602. ast_log(LOG_ERROR, "Updating on column '%s', but that column does not exist within the table '%s'!\n", field->name, tablename);
  603. release_table(table);
  604. release_database(dbh);
  605. return -1;
  606. }
  607. ESCAPE_STRING(buf, field->value);
  608. ast_str_append(&where, 0, "%s `%s` = '%s'", first ? "" : " AND", field->name, ast_str_buffer(buf));
  609. first = 0;
  610. }
  611. first = 1;
  612. for (field = update_fields; field; field = field->next) {
  613. /* If the column is not within the table, then skip it */
  614. if (!(column = find_column(table, field->name))) {
  615. ast_log(LOG_WARNING, "Attempted to update column '%s' in table '%s', but column does not exist!\n", field->name, tablename);
  616. continue;
  617. }
  618. ESCAPE_STRING(buf, field->value);
  619. ast_str_append(&sql, 0, "%s `%s` = '%s'", first ? "" : ",", field->name, ast_str_buffer(buf));
  620. first = 0;
  621. }
  622. release_table(table);
  623. ast_str_append(&sql, 0, " %s", ast_str_buffer(where));
  624. ast_debug(1, "MySQL RealTime: Update SQL: %s\n", ast_str_buffer(sql));
  625. /* Execution. */
  626. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  627. ast_log(LOG_WARNING, "MySQL RealTime: Failed to update database: %s\n", mysql_error(&dbh->handle));
  628. release_table(table);
  629. release_database(dbh);
  630. return -1;
  631. }
  632. numrows = mysql_affected_rows(&dbh->handle);
  633. release_database(dbh);
  634. ast_debug(1, "MySQL RealTime: Updated %" PRIu64 " rows on table: %s\n", numrows, tablename);
  635. /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
  636. * An integer greater than zero indicates the number of rows affected
  637. * Zero indicates that no records were updated
  638. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  639. */
  640. return (int)numrows;
  641. }
  642. static int store_mysql(const char *database, const char *table, const struct ast_variable *rt_fields)
  643. {
  644. struct mysql_conn *dbh;
  645. struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
  646. struct ast_str *sql2 = ast_str_thread_get(&sql2_buf, 16);
  647. struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
  648. const struct ast_variable *field = rt_fields;
  649. if (!(dbh = find_database(database, 1))) {
  650. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
  651. return -1;
  652. }
  653. if (!table) {
  654. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  655. release_database(dbh);
  656. return -1;
  657. }
  658. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  659. if (!field) {
  660. ast_log(LOG_WARNING, "MySQL RealTime: Realtime storage requires at least 1 parameter and 1 value to search on.\n");
  661. release_database(dbh);
  662. return -1;
  663. }
  664. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  665. if (!mysql_reconnect(dbh)) {
  666. release_database(dbh);
  667. return -1;
  668. }
  669. /* Create the first part of the query using the first parameter/value pairs we just extracted
  670. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  671. ESCAPE_STRING(buf, field->value);
  672. ast_str_set(&sql, 0, "INSERT INTO %s (`%s`", table, field->name);
  673. ast_str_set(&sql2, 0, ") VALUES ('%s'", ast_str_buffer(buf));
  674. while ((field = field->next)) {
  675. ESCAPE_STRING(buf, field->value);
  676. ast_str_append(&sql, 0, ", `%s`", field->name);
  677. ast_str_append(&sql2, 0, ", '%s'", ast_str_buffer(buf));
  678. }
  679. ast_str_append(&sql, 0, "%s)", ast_str_buffer(sql2));
  680. ast_debug(1,"MySQL RealTime: Insert SQL: %s\n", ast_str_buffer(sql));
  681. /* Execution. */
  682. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  683. ast_log(LOG_WARNING, "MySQL RealTime: Failed to insert into database: %s\n", mysql_error(&dbh->handle));
  684. release_database(dbh);
  685. return -1;
  686. }
  687. release_database(dbh);
  688. ast_debug(1, "MySQL RealTime: row inserted on table: %s\n", table);
  689. return 1;
  690. }
  691. static int destroy_mysql(const char *database, const char *table, const char *keyfield, const char *lookup, const struct ast_variable *rt_fields)
  692. {
  693. struct mysql_conn *dbh;
  694. uint64_t numrows;
  695. struct ast_str *sql = ast_str_thread_get(&sql_buf, 16);
  696. struct ast_str *buf = ast_str_thread_get(&scratch_buf, 16);
  697. const struct ast_variable *field;
  698. if (!(dbh = find_database(database, 1))) {
  699. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
  700. return -1;
  701. }
  702. if (!table) {
  703. ast_log(LOG_WARNING, "MySQL RealTime: No table specified.\n");
  704. release_database(dbh);
  705. return -1;
  706. }
  707. /* Get the first parameter and first value in our list of passed paramater/value pairs */
  708. /* newparam = va_arg(ap, const char *);
  709. newval = va_arg(ap, const char *);*/
  710. if (ast_strlen_zero(keyfield) || ast_strlen_zero(lookup)) {
  711. ast_log(LOG_WARNING, "MySQL RealTime: Realtime destroying requires at least 1 parameter and 1 value to search on.\n");
  712. release_database(dbh);
  713. return -1;
  714. }
  715. /* Must connect to the server before anything else, as the escape function requires the mysql handle. */
  716. if (!mysql_reconnect(dbh)) {
  717. release_database(dbh);
  718. return -1;
  719. }
  720. /* Create the first part of the query using the first parameter/value pairs we just extracted
  721. If there is only 1 set, then we have our query. Otherwise, loop thru the list and concat */
  722. ESCAPE_STRING(buf, lookup);
  723. ast_str_set(&sql, 0, "DELETE FROM %s WHERE `%s` = '%s'", table, keyfield, ast_str_buffer(buf));
  724. for (field = rt_fields; field; field = field->next) {
  725. ESCAPE_STRING(buf, field->value);
  726. ast_str_append(&sql, 0, " AND `%s` = '%s'", field->name, ast_str_buffer(buf));
  727. }
  728. ast_debug(1, "MySQL RealTime: Delete SQL: %s\n", ast_str_buffer(sql));
  729. /* Execution. */
  730. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  731. ast_log(LOG_WARNING, "MySQL RealTime: Failed to delete from database: %s\n", mysql_error(&dbh->handle));
  732. release_database(dbh);
  733. return -1;
  734. }
  735. numrows = mysql_affected_rows(&dbh->handle);
  736. release_database(dbh);
  737. ast_debug(1, "MySQL RealTime: Deleted %" PRIu64 " rows on table: %s\n", numrows, table);
  738. /* From http://dev.mysql.com/doc/mysql/en/mysql-affected-rows.html
  739. * An integer greater than zero indicates the number of rows affected
  740. * Zero indicates that no records were updated
  741. * -1 indicates that the query returned an error (although, if the query failed, it should have been caught above.)
  742. */
  743. return (int)numrows;
  744. }
  745. static struct ast_config *config_mysql(const char *database, const char *table, const char *file, struct ast_config *cfg, struct ast_flags config_flags, const char *unused, const char *who_asked)
  746. {
  747. struct mysql_conn *dbh;
  748. MYSQL_RES *result;
  749. MYSQL_ROW row;
  750. uint64_t num_rows;
  751. struct ast_variable *new_v;
  752. struct ast_category *cur_cat = NULL;
  753. struct ast_str *sql = ast_str_thread_get(&sql_buf, 200);
  754. char last[80] = "";
  755. int last_cat_metric = 0;
  756. ast_clear_flag(&config_flags, CONFIG_FLAG_FILEUNCHANGED);
  757. if (!file || !strcmp(file, RES_CONFIG_MYSQL_CONF)) {
  758. ast_log(LOG_WARNING, "MySQL RealTime: Cannot configure myself.\n");
  759. return NULL;
  760. }
  761. if (!(dbh = find_database(database, 0))) {
  762. ast_log(LOG_WARNING, "MySQL RealTime: Invalid database specified: '%s' (check res_mysql.conf)\n", database);
  763. return NULL;
  764. }
  765. ast_str_set(&sql, 0, "SELECT category, var_name, var_val, cat_metric FROM %s WHERE filename='%s' and commented=0 ORDER BY filename, category, cat_metric desc, var_metric asc, var_name, var_val, id", table, file);
  766. ast_debug(1, "MySQL RealTime: Static SQL: %s\n", ast_str_buffer(sql));
  767. /* We now have our complete statement; Lets connect to the server and execute it. */
  768. if (!mysql_reconnect(dbh)) {
  769. return NULL;
  770. }
  771. if (mysql_real_query(&dbh->handle, ast_str_buffer(sql), ast_str_strlen(sql))) {
  772. ast_log(LOG_WARNING, "MySQL RealTime: Failed to query database. Check debug for more info.\n");
  773. ast_debug(1, "MySQL RealTime: Query: %s\n", ast_str_buffer(sql));
  774. ast_debug(1, "MySQL RealTime: Query Failed because: %s\n", mysql_error(&dbh->handle));
  775. release_database(dbh);
  776. return NULL;
  777. }
  778. if ((result = mysql_store_result(&dbh->handle))) {
  779. num_rows = mysql_num_rows(result);
  780. ast_debug(1, "MySQL RealTime: Found %" PRIu64 " rows.\n", num_rows);
  781. /* There might exist a better way to access the column names other than counting,
  782. * but I believe that would require another loop that we don't need. */
  783. while ((row = mysql_fetch_row(result))) {
  784. if (!strcmp(row[1], "#include")) {
  785. if (!ast_config_internal_load(row[2], cfg, config_flags, "", who_asked)) {
  786. mysql_free_result(result);
  787. release_database(dbh);
  788. return NULL;
  789. }
  790. continue;
  791. }
  792. if (strcmp(last, row[0]) || last_cat_metric != atoi(row[3])) {
  793. cur_cat = ast_category_new_dynamic(row[0]);
  794. if (!cur_cat) {
  795. break;
  796. }
  797. strcpy(last, row[0]);
  798. last_cat_metric = atoi(row[3]);
  799. ast_category_append(cfg, cur_cat);
  800. }
  801. new_v = ast_variable_new(row[1], row[2], "");
  802. if (cur_cat)
  803. ast_variable_append(cur_cat, new_v);
  804. }
  805. } else {
  806. ast_log(LOG_WARNING, "MySQL RealTime: Could not find config '%s' in database.\n", file);
  807. }
  808. mysql_free_result(result);
  809. release_database(dbh);
  810. return cfg;
  811. }
  812. static int unload_mysql(const char *database, const char *tablename)
  813. {
  814. struct tables *cur;
  815. AST_LIST_LOCK(&mysql_tables);
  816. AST_LIST_TRAVERSE_SAFE_BEGIN(&mysql_tables, cur, list) {
  817. if (strcmp(cur->name, tablename) == 0) {
  818. AST_LIST_REMOVE_CURRENT(list);
  819. destroy_table(cur);
  820. break;
  821. }
  822. }
  823. AST_LIST_TRAVERSE_SAFE_END
  824. AST_LIST_UNLOCK(&mysql_tables);
  825. return cur ? 0 : -1;
  826. }
  827. static int require_mysql(const char *database, const char *tablename, va_list ap)
  828. {
  829. struct columns *column;
  830. struct tables *table = find_table(database, tablename);
  831. char *elm;
  832. int type;
  833. int size;
  834. int res = 0;
  835. if (!table) {
  836. ast_log(LOG_WARNING, "Table %s not found in database. This table should exist if you're using realtime.\n", tablename);
  837. return -1;
  838. }
  839. while ((elm = va_arg(ap, char *))) {
  840. type = va_arg(ap, require_type);
  841. size = va_arg(ap, int);
  842. AST_LIST_TRAVERSE(&table->columns, column, list) {
  843. if (strcmp(column->name, elm) == 0) {
  844. /* Char can hold anything, as long as it is large enough */
  845. if (strncmp(column->type, "char", 4) == 0 || strncmp(column->type, "varchar", 7) == 0) {
  846. if ((size > column->len) && column->len != -1) {
  847. ast_log(LOG_WARNING, "Realtime table %s@%s: Column '%s' should be at least %d long, but is only %d long.\n", database, tablename, column->name, size, column->len);
  848. res = -1;
  849. }
  850. } else if (strcasestr(column->type, "unsigned")) {
  851. if (!ast_rq_is_int(type)) {
  852. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
  853. database, tablename, column->name, column->type,
  854. type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
  855. type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" : "a rather stiff drink");
  856. res = -1;
  857. } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
  858. if (type != RQ_UINTEGER1) {
  859. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  860. "the required data length: %d (detected stringtype)\n", \
  861. tablename, database, column->name, size); \
  862. res = -1; \
  863. }
  864. } else if (strncasecmp(column->type, "smallint", 1) == 0) {
  865. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_UINTEGER2) {
  866. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  867. "the required data length: %d (detected stringtype)\n", \
  868. tablename, database, column->name, size); \
  869. res = -1; \
  870. }
  871. } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
  872. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  873. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  874. type != RQ_UINTEGER3) {
  875. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  876. "the required data length: %d (detected stringtype)\n", \
  877. tablename, database, column->name, size); \
  878. res = -1; \
  879. }
  880. } else if (strncasecmp(column->type, "int", 1) == 0) {
  881. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  882. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  883. type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
  884. type != RQ_UINTEGER4) {
  885. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  886. "the required data length: %d (detected stringtype)\n", \
  887. tablename, database, column->name, size); \
  888. res = -1; \
  889. }
  890. } else if (strncasecmp(column->type, "bigint", 1) == 0) {
  891. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  892. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  893. type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
  894. type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
  895. type != RQ_UINTEGER8) {
  896. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  897. "the required data length: %d (detected stringtype)\n", \
  898. tablename, database, column->name, size); \
  899. res = -1; \
  900. }
  901. }
  902. } else if (strcasestr(column->type, "int")) {
  903. if (!ast_rq_is_int(type)) {
  904. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' cannot be type '%s' (need %s)\n",
  905. database, tablename, column->name, column->type,
  906. type == RQ_CHAR ? "char" : type == RQ_FLOAT ? "float" :
  907. type == RQ_DATETIME ? "datetime" : type == RQ_DATE ? "date" :
  908. "to get a life, rather than writing silly error messages");
  909. res = -1;
  910. } else if (strncasecmp(column->type, "tinyint", 1) == 0) {
  911. if (type != RQ_INTEGER1) {
  912. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  913. "the required data length: %d (detected stringtype)\n", \
  914. tablename, database, column->name, size); \
  915. res = -1; \
  916. }
  917. } else if (strncasecmp(column->type, "smallint", 1) == 0) {
  918. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 && type != RQ_INTEGER2) {
  919. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  920. "the required data length: %d (detected stringtype)\n", \
  921. tablename, database, column->name, size); \
  922. res = -1; \
  923. }
  924. } else if (strncasecmp(column->type, "mediumint", 1) == 0) {
  925. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  926. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  927. type != RQ_INTEGER3) {
  928. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  929. "the required data length: %d (detected stringtype)\n", \
  930. tablename, database, column->name, size); \
  931. res = -1; \
  932. }
  933. } else if (strncasecmp(column->type, "int", 1) == 0) {
  934. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  935. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  936. type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
  937. type != RQ_INTEGER4) {
  938. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  939. "the required data length: %d (detected stringtype)\n", \
  940. tablename, database, column->name, size); \
  941. res = -1; \
  942. }
  943. } else if (strncasecmp(column->type, "bigint", 1) == 0) {
  944. if (type != RQ_UINTEGER1 && type != RQ_INTEGER1 &&
  945. type != RQ_UINTEGER2 && type != RQ_INTEGER2 &&
  946. type != RQ_UINTEGER3 && type != RQ_INTEGER3 &&
  947. type != RQ_UINTEGER4 && type != RQ_INTEGER4 &&
  948. type != RQ_INTEGER8) {
  949. ast_log(LOG_WARNING, "Realtime table %s@%s: column '%s' may not be large enough for " \
  950. "the required data length: %d (detected stringtype)\n", \
  951. tablename, database, column->name, size); \
  952. res = -1; \
  953. }
  954. }
  955. } else if (strncmp(column->type, "float", 5) == 0) {
  956. if (!ast_rq_is_int(type) && type != RQ_FLOAT) {
  957. ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
  958. res = -1;
  959. }
  960. } else if (strncmp(column->type, "datetime", 8) == 0 || strncmp(column->type, "timestamp", 9) == 0) {
  961. if (type != RQ_DATETIME) {
  962. ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
  963. res = -1;
  964. }
  965. } else if (strncmp(column->type, "date", 4) == 0) {
  966. if (type != RQ_DATE) {
  967. ast_log(LOG_WARNING, "Realtime table %s@%s: Column %s cannot be a %s\n", tablename, database, column->name, column->type);
  968. res = -1;
  969. }
  970. } else { /* Other, possibly unsupported types? */
  971. ast_log(LOG_WARNING, "Possibly unsupported column type '%s' on column '%s'\n", column->type, column->name);
  972. res = -1;
  973. }
  974. break;
  975. }
  976. }
  977. if (!column) {
  978. ast_log(LOG_WARNING, "Table %s requires a column '%s' of size '%d', but no such column exists.\n", tablename, elm, size);
  979. }
  980. }
  981. release_table(table);
  982. return res;
  983. }
  984. static struct ast_config_engine mysql_engine = {
  985. .name = "mysql",
  986. .load_func = config_mysql,
  987. .realtime_func = realtime_mysql,
  988. .realtime_multi_func = realtime_multi_mysql,
  989. .store_func = store_mysql,
  990. .destroy_func = destroy_mysql,
  991. .update_func = update_mysql,
  992. .update2_func = update2_mysql,
  993. .require_func = require_mysql,
  994. .unload_func = unload_mysql,
  995. };
  996. static int load_module(void)
  997. {
  998. parse_config(0);
  999. ast_config_engine_register(&mysql_engine);
  1000. ast_verb(2, "MySQL RealTime driver loaded.\n");
  1001. ast_cli_register_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
  1002. return 0;
  1003. }
  1004. static int unload_module(void)
  1005. {
  1006. struct mysql_conn *cur;
  1007. struct tables *table;
  1008. ast_cli_unregister_multiple(cli_realtime_mysql_status, sizeof(cli_realtime_mysql_status) / sizeof(struct ast_cli_entry));
  1009. ast_config_engine_deregister(&mysql_engine);
  1010. ast_verb(2, "MySQL RealTime unloaded.\n");
  1011. AST_RWLIST_WRLOCK(&databases);
  1012. while ((cur = AST_RWLIST_REMOVE_HEAD(&databases, list))) {
  1013. mysql_close(&cur->handle);
  1014. ast_mutex_destroy(&cur->lock);
  1015. ast_free(cur);
  1016. }
  1017. AST_RWLIST_UNLOCK(&databases);
  1018. /* Destroy cached table info */
  1019. AST_LIST_LOCK(&mysql_tables);
  1020. while ((table = AST_LIST_REMOVE_HEAD(&mysql_tables, list))) {
  1021. destroy_table(table);
  1022. }
  1023. AST_LIST_UNLOCK(&mysql_tables);
  1024. return 0;
  1025. }
  1026. static int reload(void)
  1027. {
  1028. parse_config(1);
  1029. ast_verb(2, "MySQL RealTime reloaded.\n");
  1030. return 0;
  1031. }
  1032. static int parse_config(int reload)
  1033. {
  1034. struct ast_config *config = NULL;
  1035. struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
  1036. const char *catg;
  1037. struct mysql_conn *cur;
  1038. if ((config = ast_config_load(RES_CONFIG_MYSQL_CONF, config_flags)) == CONFIG_STATUS_FILEMISSING) {
  1039. /* Support old config file name */
  1040. config = ast_config_load(RES_CONFIG_MYSQL_CONF_OLD, config_flags);
  1041. }
  1042. if (config == CONFIG_STATUS_FILEMISSING) {
  1043. return 0;
  1044. } else if (config == CONFIG_STATUS_FILEUNCHANGED) {
  1045. return 0;
  1046. } else if (config == CONFIG_STATUS_FILEINVALID) {
  1047. ast_log(LOG_ERROR, "Not %sloading " RES_CONFIG_MYSQL_CONF "\n", reload ? "re" : "");
  1048. }
  1049. AST_RWLIST_WRLOCK(&databases);
  1050. for (catg = ast_category_browse(config, NULL); catg; catg = ast_category_browse(config, catg)) {
  1051. /* Does this category already exist? */
  1052. AST_RWLIST_TRAVERSE(&databases, cur, list) {
  1053. if (!strcmp(cur->unique_name, catg)) {
  1054. break;
  1055. }
  1056. }
  1057. if (!cur) {
  1058. if (!(cur = ast_calloc(1, sizeof(*cur) + strlen(catg) + 1))) {
  1059. ast_log(LOG_WARNING, "Could not allocate space for MySQL database '%s'\n", catg);
  1060. continue;
  1061. }
  1062. strcpy(cur->unique_name, catg); /* SAFE */
  1063. ast_mutex_init(&cur->lock);
  1064. AST_RWLIST_INSERT_TAIL(&databases, cur, list);
  1065. }
  1066. load_mysql_config(config, catg, cur);
  1067. }
  1068. AST_RWLIST_UNLOCK(&databases);
  1069. ast_config_destroy(config);
  1070. return 0;
  1071. }
  1072. static int load_mysql_config(struct ast_config *config, const char *category, struct mysql_conn *conn)
  1073. {
  1074. const char *s;
  1075. if (!(s = ast_variable_retrieve(config, category, "dbuser"))) {
  1076. ast_log(LOG_WARNING, "MySQL RealTime: No database user found, using 'asterisk' as default.\n");
  1077. s = "asterisk";
  1078. }
  1079. ast_copy_string(conn->user, s, sizeof(conn->user));
  1080. if (!(s = ast_variable_retrieve(config, category, "dbpass"))) {
  1081. ast_log(LOG_WARNING, "MySQL RealTime: No database password found, using 'asterisk' as default.\n");
  1082. s = "asterisk";
  1083. }
  1084. ast_copy_string(conn->pass, s, sizeof(conn->pass));
  1085. if (!(s = ast_variable_retrieve(config, category, "dbhost"))) {
  1086. ast_log(LOG_WARNING, "MySQL RealTime: No database host found, using localhost via socket.\n");
  1087. s = "";
  1088. }
  1089. ast_copy_string(conn->host, s, sizeof(conn->host));
  1090. if (!(s = ast_variable_retrieve(config, category, "dbname"))) {
  1091. ast_log(LOG_WARNING, "MySQL RealTime: No database name found, using 'asterisk' as default.\n");
  1092. s = "asterisk";
  1093. }
  1094. ast_copy_string(conn->name, s, sizeof(conn->name));
  1095. if (!(s = ast_variable_retrieve(config, category, "dbport"))) {
  1096. ast_log(LOG_WARNING, "MySQL RealTime: No database port found, using 3306 as default.\n");
  1097. conn->port = 3306;
  1098. } else
  1099. conn->port = atoi(s);
  1100. if (!(s = ast_variable_retrieve(config, category, "dbsock"))) {
  1101. if (ast_strlen_zero(conn->host)) {
  1102. char *paths[3] = { "/tmp/mysql.sock", "/var/lib/mysql/mysql.sock", "/var/run/mysqld/mysqld.sock" };
  1103. struct stat st;
  1104. int i;
  1105. for (i = 0; i < 3; i++) {
  1106. if (!stat(paths[i], &st)) {
  1107. ast_log(LOG_WARNING, "MySQL RealTime: No database socket found, using '%s' as default.\n", paths[i]);
  1108. ast_copy_string(conn->sock, paths[i], sizeof(conn->sock));
  1109. }
  1110. }
  1111. if (i == 3) {
  1112. ast_log(LOG_WARNING, "MySQL RealTime: No database socket found (and unable to detect a suitable path).\n");
  1113. return 0;
  1114. }
  1115. }
  1116. } else
  1117. ast_copy_string(conn->sock, s, sizeof(conn->sock));
  1118. if ((s = ast_variable_retrieve(config, category, "dbcharset"))) {
  1119. ast_copy_string(conn->charset, s, sizeof(conn->charset));
  1120. }
  1121. if (!(s = ast_variable_retrieve(config, category, "requirements"))) {
  1122. ast_log(LOG_WARNING, "MySQL realtime: no requirements setting found, using 'warn' as default.\n");
  1123. conn->requirements = RQ_WARN;
  1124. } else if (!strcasecmp(s, "createclose")) {
  1125. conn->requirements = RQ_CREATECLOSE;
  1126. } else if (!strcasecmp(s, "createchar")) {
  1127. conn->requirements = RQ_CREATECHAR;
  1128. } else if (!strcasecmp(s, "warn")) {
  1129. conn->requirements = RQ_WARN;
  1130. } else {
  1131. ast_log(LOG_WARNING, "MySQL realtime: unrecognized requirements setting '%s', using 'warn'\n", s);
  1132. conn->requirements = RQ_WARN;
  1133. }
  1134. if (!ast_strlen_zero(conn->host)) {
  1135. ast_debug(1, "MySQL RealTime host: %s\n", conn->host);
  1136. ast_debug(1, "MySQL RealTime port: %i\n", conn->port);
  1137. } else
  1138. ast_debug(1, "MySQL RealTime socket: %s\n", conn->sock);
  1139. ast_debug(1, "MySQL RealTime database name: %s\n", conn->name);
  1140. ast_debug(1, "MySQL RealTime user: %s\n", conn->user);
  1141. ast_debug(1, "MySQL RealTime password: %s\n", conn->pass);
  1142. if(!ast_strlen_zero(conn->charset))
  1143. ast_debug(1, "MySQL RealTime charset: %s\n", conn->charset);
  1144. return 1;
  1145. }
  1146. static int mysql_reconnect(struct mysql_conn *conn)
  1147. {
  1148. #ifdef MYSQL_OPT_RECONNECT
  1149. my_bool trueval = 1;
  1150. #endif
  1151. /* mutex lock should have been locked before calling this function. */
  1152. reconnect_tryagain:
  1153. if ((!conn->connected) && (!ast_strlen_zero(conn->host) || !ast_strlen_zero(conn->sock)) && !ast_strlen_zero(conn->user) && !ast_strlen_zero(conn->name)) {
  1154. if (!mysql_init(&conn->handle)) {
  1155. ast_log(LOG_WARNING, "MySQL RealTime: Insufficient memory to allocate MySQL resource.\n");
  1156. conn->connected = 0;
  1157. return 0;
  1158. }
  1159. if(strlen(conn->charset) > 2){
  1160. char set_names[255];
  1161. char statement[512];
  1162. snprintf(set_names, sizeof(set_names), "SET NAMES %s", conn->charset);
  1163. mysql_real_escape_string(&conn->handle, statement, set_names, sizeof(set_names));
  1164. mysql_options(&conn->handle, MYSQL_INIT_COMMAND, set_names);
  1165. mysql_options(&conn->handle, MYSQL_SET_CHARSET_NAME, conn->charset);
  1166. }
  1167. if (mysql_real_connect(&conn->handle, conn->host, conn->user, conn->pass, conn->name, conn->port, conn->sock, 0)) {
  1168. #ifdef MYSQL_OPT_RECONNECT
  1169. /* The default is no longer to automatically reconnect on failure,
  1170. * (as of 5.0.3) so we have to set that option here. */
  1171. mysql_options(&conn->handle, MYSQL_OPT_RECONNECT, &trueval);
  1172. #endif
  1173. ast_debug(1, "MySQL RealTime: Successfully connected to database.\n");
  1174. conn->connected = 1;
  1175. conn->connect_time = time(NULL);
  1176. return 1;
  1177. } else {
  1178. ast_log(LOG_ERROR, "MySQL RealTime: Failed to connect database server %s on %s (err %d). Check debug for more info.\n", conn->name, !ast_strlen_zero(conn->host) ? conn->host : conn->sock, mysql_errno(&conn->handle));
  1179. ast_debug(1, "MySQL RealTime: Cannot Connect (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
  1180. conn->connected = 0;
  1181. conn->connect_time = 0;
  1182. return 0;
  1183. }
  1184. } else {
  1185. /* MySQL likes to return an error, even if it reconnects successfully.
  1186. * So the postman pings twice. */
  1187. if (mysql_ping(&conn->handle) != 0 && (usleep(1) + 2 > 0) && mysql_ping(&conn->handle) != 0) {
  1188. conn->connected = 0;
  1189. conn->connect_time = 0;
  1190. ast_log(LOG_ERROR, "MySQL RealTime: Ping failed (%d). Trying an explicit reconnect.\n", mysql_errno(&conn->handle));
  1191. ast_debug(1, "MySQL RealTime: Server Error (%d): %s\n", mysql_errno(&conn->handle), mysql_error(&conn->handle));
  1192. goto reconnect_tryagain;
  1193. }
  1194. if (!conn->connected) {
  1195. conn->connected = 1;
  1196. conn->connect_time = time(NULL);
  1197. }
  1198. if (mysql_select_db(&conn->handle, conn->name) != 0) {
  1199. ast_log(LOG_WARNING, "MySQL RealTime: Unable to select database: %s. Still Connected (%u) - %s.\n", conn->name, mysql_errno(&conn->handle), mysql_error(&conn->handle));
  1200. return 0;
  1201. }
  1202. ast_debug(1, "MySQL RealTime: Connection okay.\n");
  1203. return 1;
  1204. }
  1205. }
  1206. static char *handle_cli_realtime_mysql_cache(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  1207. {
  1208. struct tables *cur;
  1209. int l, which;
  1210. char *ret = NULL;
  1211. switch (cmd) {
  1212. case CLI_INIT:
  1213. e->command = "realtime mysql cache";
  1214. e->usage =
  1215. "Usage: realtime mysql cache [<database> <table>]\n"
  1216. " Shows table cache for the MySQL RealTime driver\n";
  1217. return NULL;
  1218. case CLI_GENERATE:
  1219. if (a->argc < 4 || a->argc > 5) {
  1220. return NULL;
  1221. }
  1222. l = strlen(a->word);
  1223. which = 0;
  1224. if (a->argc == 5) {
  1225. AST_LIST_LOCK(&mysql_tables);
  1226. AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
  1227. if (!strcasecmp(a->argv[3], cur->database->unique_name) && !strncasecmp(a->word, cur->name, l) && ++which > a->n) {
  1228. ret = ast_strdup(cur->name);
  1229. break;
  1230. }
  1231. }
  1232. AST_LIST_UNLOCK(&mysql_tables);
  1233. } else {
  1234. struct mysql_conn *cur;
  1235. AST_RWLIST_RDLOCK(&databases);
  1236. AST_RWLIST_TRAVERSE(&databases, cur, list) {
  1237. if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
  1238. ret = ast_strdup(cur->unique_name);
  1239. break;
  1240. }
  1241. }
  1242. AST_RWLIST_UNLOCK(&databases);
  1243. }
  1244. return ret;
  1245. }
  1246. if (a->argc == 3) {
  1247. /* List of tables */
  1248. AST_LIST_LOCK(&mysql_tables);
  1249. AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
  1250. ast_cli(a->fd, "%20.20s %s\n", cur->database->unique_name, cur->name);
  1251. }
  1252. AST_LIST_UNLOCK(&mysql_tables);
  1253. } else if (a->argc == 4) {
  1254. int found = 0;
  1255. /* List of tables */
  1256. AST_LIST_LOCK(&mysql_tables);
  1257. AST_LIST_TRAVERSE(&mysql_tables, cur, list) {
  1258. if (!strcasecmp(cur->database->unique_name, a->argv[3])) {
  1259. ast_cli(a->fd, "%s\n", cur->name);
  1260. found = 1;
  1261. }
  1262. }
  1263. AST_LIST_UNLOCK(&mysql_tables);
  1264. if (!found) {
  1265. ast_cli(a->fd, "No tables cached within %s database\n", a->argv[3]);
  1266. }
  1267. } else if (a->argc == 5) {
  1268. /* List of columns */
  1269. if ((cur = find_table(a->argv[3], a->argv[4]))) {
  1270. struct columns *col;
  1271. ast_cli(a->fd, "Columns for Table Cache '%s':\n", a->argv[3]);
  1272. ast_cli(a->fd, "%-20.20s %-20.20s %-3.3s\n", "Name", "Type", "Len");
  1273. AST_LIST_TRAVERSE(&cur->columns, col, list) {
  1274. ast_cli(a->fd, "%-20.20s %-20.20s %3d\n", col->name, col->type, col->len);
  1275. }
  1276. release_table(cur);
  1277. } else {
  1278. ast_cli(a->fd, "No such table '%s'\n", a->argv[3]);
  1279. }
  1280. }
  1281. return CLI_SUCCESS;
  1282. }
  1283. static char *handle_cli_realtime_mysql_status(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  1284. {
  1285. char status[256], status2[100] = "", type[20];
  1286. char *ret = NULL;
  1287. int ctime = 0, found = 0;
  1288. struct mysql_conn *cur;
  1289. int l = 0, which = 0;
  1290. switch (cmd) {
  1291. case CLI_INIT:
  1292. e->command = "realtime mysql status";
  1293. e->usage =
  1294. "Usage: realtime mysql status [<database>]\n"
  1295. " Shows connection information for the MySQL RealTime driver\n";
  1296. return NULL;
  1297. case CLI_GENERATE:
  1298. if (a->argc == 4) {
  1299. AST_RWLIST_RDLOCK(&databases);
  1300. AST_RWLIST_TRAVERSE(&databases, cur, list) {
  1301. if (!strncasecmp(a->word, cur->unique_name, l) && ++which > a->n) {
  1302. ret = ast_strdup(cur->unique_name);
  1303. break;
  1304. }
  1305. }
  1306. AST_RWLIST_UNLOCK(&databases);
  1307. }
  1308. return ret;
  1309. }
  1310. if (a->argc != 3)
  1311. return CLI_SHOWUSAGE;
  1312. AST_RWLIST_RDLOCK(&databases);
  1313. AST_RWLIST_TRAVERSE(&databases, cur, list) {
  1314. if (a->argc == 3 || (a->argc == 4 && !strcasecmp(a->argv[3], cur->unique_name))) {
  1315. found = 1;
  1316. if (mysql_reconnect(cur)) {
  1317. snprintf(type, sizeof(type), "connected to");
  1318. ctime = time(NULL) - cur->connect_time;
  1319. } else {
  1320. snprintf(type, sizeof(type), "configured for");
  1321. ctime = -1;
  1322. }
  1323. if (!ast_strlen_zero(cur->host)) {
  1324. snprintf(status, sizeof(status), "%s %s %s@%s, port %d", cur->unique_name, type, cur->name, cur->host, cur->port);
  1325. } else {
  1326. snprintf(status, sizeof(status), "%s %s %s on socket file %s", cur->unique_name, type, cur->name, cur->sock);
  1327. }
  1328. if (!ast_strlen_zero(cur->user)) {
  1329. snprintf(status2, sizeof(status2), " with username %s", cur->user);
  1330. } else {
  1331. status2[0] = '\0';
  1332. }
  1333. if (ctime > 31536000) {
  1334. ast_cli(a->fd, "%s%s for %.1f years.\n", status, status2, (double)ctime / 31536000.0);
  1335. } else if (ctime > 86400 * 30) {
  1336. ast_cli(a->fd, "%s%s for %d days.\n", status, status2, ctime / 86400);
  1337. } else if (ctime > 86400) {
  1338. ast_cli(a->fd, "%s%s for %d days, %d hours.\n", status, status2, ctime / 86400, (ctime % 86400) / 3600);
  1339. } else if (ctime > 3600) {
  1340. ast_cli(a->fd, "%s%s for %d hours, %d minutes.\n", status, status2, ctime / 3600, (ctime % 3600) / 60);
  1341. } else if (ctime > 60) {
  1342. ast_cli(a->fd, "%s%s for %d minutes.\n", status, status2, ctime / 60);
  1343. } else if (ctime > -1) {
  1344. ast_cli(a->fd, "%s%s for %d seconds.\n", status, status2, ctime);
  1345. } else {
  1346. ast_cli(a->fd, "%s%s.\n", status, status2);
  1347. }
  1348. }
  1349. }
  1350. AST_RWLIST_UNLOCK(&databases);
  1351. if (!found) {
  1352. ast_cli(a->fd, "No connections configured.\n");
  1353. }
  1354. return CLI_SUCCESS;
  1355. }
  1356. AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "MySQL RealTime Configuration Driver",
  1357. .support_level = AST_MODULE_SUPPORT_EXTENDED,
  1358. .load = load_module,
  1359. .unload = unload_module,
  1360. .reload = reload,
  1361. .load_pri = AST_MODPRI_REALTIME_DRIVER,
  1362. .requires = "extconfig",
  1363. );