dr_load.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. /*
  2. * $Id$
  3. *
  4. * Copyright (C) 2005-2009 Voice Sistem SRL
  5. *
  6. * This file is part of Kamailio, a free SIP server.
  7. *
  8. * Kamailio is free software; you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation; either version 2 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Kamailio is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21. *
  22. * History:
  23. * ---------
  24. * 2005-02-20 first version (cristian)
  25. * 2005-02-27 ported to 0.9.0 (bogdan)
  26. */
  27. #include <string.h>
  28. #include <ctype.h>
  29. #include <stdlib.h>
  30. #include <errno.h>
  31. #include <limits.h>
  32. #include "../../dprint.h"
  33. #include "../../route.h"
  34. //#include "../../db/db.h"
  35. #include "../../mem/shm_mem.h"
  36. #include "dr_load.h"
  37. #include "routing.h"
  38. #include "prefix_tree.h"
  39. #include "dr_time.h"
  40. #include "parse.h"
  41. #define DST_ID_DRD_COL "gwid"
  42. #define ADDRESS_DRD_COL "address"
  43. #define STRIP_DRD_COL "strip"
  44. #define PREFIX_DRD_COL "pri_prefix"
  45. #define TYPE_DRD_COL "type"
  46. #define ATTRS_DRD_COL "attrs"
  47. static str dst_id_drd_col = str_init(DST_ID_DRD_COL);
  48. static str address_drd_col = str_init(ADDRESS_DRD_COL);
  49. static str strip_drd_col = str_init(STRIP_DRD_COL);
  50. static str prefix_drd_col = str_init(PREFIX_DRD_COL);
  51. static str type_drd_col = str_init(TYPE_DRD_COL);
  52. static str attrs_drd_col = str_init(ATTRS_DRD_COL);
  53. #define RULE_ID_DRR_COL "ruleid"
  54. #define GROUP_DRR_COL "groupid"
  55. #define PREFIX_DRR_COL "prefix"
  56. #define TIME_DRR_COL "timerec"
  57. #define PRIORITY_DRR_COL "priority"
  58. #define ROUTEID_DRR_COL "routeid"
  59. #define DSTLIST_DRR_COL "gwlist"
  60. static str rule_id_drr_col = str_init(RULE_ID_DRR_COL);
  61. static str group_drr_col = str_init(GROUP_DRR_COL);
  62. static str prefix_drr_col = str_init(PREFIX_DRR_COL);
  63. static str time_drr_col = str_init(TIME_DRR_COL);
  64. static str priority_drr_col = str_init(PRIORITY_DRR_COL);
  65. static str routeid_drr_col = str_init(ROUTEID_DRR_COL);
  66. static str dstlist_drr_col = str_init(DSTLIST_DRR_COL);
  67. #define ID_DRL_COL "id"
  68. #define GWLIST_DRL_CAL "gwlist"
  69. static str id_drl_col = str_init(ID_DRL_COL);
  70. static str gwlist_drl_col = str_init(GWLIST_DRL_CAL);
  71. struct dr_gwl_tmp {
  72. unsigned int id;
  73. char *gwlist;
  74. struct dr_gwl_tmp *next;
  75. };
  76. static struct dr_gwl_tmp* dr_gw_lists = NULL;
  77. #define check_val( _val, _type, _not_null, _is_empty_str) \
  78. do{\
  79. if ((_val)->type!=_type) { \
  80. LM_ERR("bad colum type\n");\
  81. goto error;\
  82. } \
  83. if (_not_null && (_val)->nul) { \
  84. LM_ERR("nul column\n");\
  85. goto error;\
  86. } \
  87. if (_is_empty_str && VAL_STRING(_val)==0) { \
  88. LM_ERR("empty str column\n");\
  89. goto error;\
  90. } \
  91. }while(0)
  92. #define TR_SEPARATOR '|'
  93. #define load_TR_value( _p,_s, _tr, _func, _err, _done) \
  94. do{ \
  95. _s = strchr(_p, (int)TR_SEPARATOR); \
  96. if (_s) \
  97. *_s = 0; \
  98. /*DBG("----parsing tr param <%s>\n",_p);*/\
  99. if(_s != _p) {\
  100. if( _func( _tr, _p)) {\
  101. if (_s) *_s = TR_SEPARATOR; \
  102. goto _err; \
  103. } \
  104. } \
  105. if (_s) { \
  106. *_s = TR_SEPARATOR; \
  107. _p = _s+1;\
  108. if ( *(_p)==0 ) \
  109. goto _done; \
  110. } else {\
  111. goto _done; \
  112. }\
  113. } while(0)
  114. extern int dr_fetch_rows;
  115. static int add_tmp_gw_list(unsigned int id, char *list)
  116. {
  117. struct dr_gwl_tmp *tmp;
  118. unsigned int list_len;
  119. list_len = strlen(list) + 1;
  120. tmp = (struct dr_gwl_tmp*)pkg_malloc(sizeof(struct dr_gwl_tmp) + list_len);
  121. if (tmp==NULL) {
  122. LM_ERR("no more pkg mem\n");
  123. return -1;
  124. }
  125. tmp->id = id;
  126. tmp->gwlist = (char*)(tmp+1);
  127. memcpy(tmp->gwlist, list, list_len);
  128. tmp->next = dr_gw_lists;
  129. dr_gw_lists = tmp;
  130. return 0;
  131. }
  132. static char* get_tmp_gw_list(unsigned int id)
  133. {
  134. struct dr_gwl_tmp *tmp;
  135. for( tmp=dr_gw_lists ; tmp ; tmp=tmp->next )
  136. if (tmp->id == id) return tmp->gwlist;
  137. return NULL;
  138. }
  139. static void free_tmp_gw_list(void)
  140. {
  141. struct dr_gwl_tmp *tmp, *tmp1;
  142. for( tmp=dr_gw_lists ; tmp ; ) {
  143. tmp1 = tmp;
  144. tmp = tmp->next;
  145. pkg_free(tmp1);
  146. }
  147. dr_gw_lists = NULL;
  148. }
  149. static inline tmrec_t* parse_time_def(char *time_str)
  150. {
  151. tmrec_t *time_rec;
  152. char *p,*s;
  153. p = time_str;
  154. time_rec = 0;
  155. time_rec = (tmrec_t*)shm_malloc(sizeof(tmrec_t));
  156. if (time_rec==0) {
  157. LM_ERR("no more pkg mem\n");
  158. goto error;
  159. }
  160. memset( time_rec, 0, sizeof(tmrec_t));
  161. /* empty definition? */
  162. if ( time_str==0 || *time_str==0 )
  163. goto done;
  164. load_TR_value( p, s, time_rec, tr_parse_dtstart, parse_error, done);
  165. load_TR_value( p, s, time_rec, tr_parse_duration, parse_error, done);
  166. load_TR_value( p, s, time_rec, tr_parse_freq, parse_error, done);
  167. load_TR_value( p, s, time_rec, tr_parse_until, parse_error, done);
  168. load_TR_value( p, s, time_rec, tr_parse_interval, parse_error, done);
  169. load_TR_value( p, s, time_rec, tr_parse_byday, parse_error, done);
  170. load_TR_value( p, s, time_rec, tr_parse_bymday, parse_error, done);
  171. load_TR_value( p, s, time_rec, tr_parse_byyday, parse_error, done);
  172. load_TR_value( p, s, time_rec, tr_parse_byweekno, parse_error, done);
  173. load_TR_value( p, s, time_rec, tr_parse_bymonth, parse_error, done);
  174. /* success */
  175. done:
  176. return time_rec;
  177. parse_error:
  178. LM_ERR("parse error in <%s> around position %i\n",
  179. time_str, (int)(long)(p-time_str));
  180. error:
  181. if (time_rec)
  182. tmrec_free( time_rec );
  183. return 0;
  184. }
  185. static int add_rule(rt_data_t *rdata, char *grplst, str *prefix, rt_info_t *rule)
  186. {
  187. long int t;
  188. char *tmp;
  189. char *ep;
  190. int n;
  191. tmp=grplst;
  192. n=0;
  193. /* parse the grplst */
  194. while(tmp && (*tmp!=0)) {
  195. errno = 0;
  196. t = strtol(tmp, &ep, 10);
  197. if (ep == tmp) {
  198. LM_ERR("bad grp id '%c' (%d)[%s]\n",
  199. *ep, (int)(ep-grplst), grplst);
  200. goto error;
  201. }
  202. if ((!IS_SPACE(*ep)) && (*ep != SEP) && (*ep != SEP1) && (*ep!=0)) {
  203. LM_ERR("bad char %c (%d) [%s]\n",
  204. *ep, (int)(ep-grplst), grplst);
  205. goto error;
  206. }
  207. if (errno == ERANGE && (t== LONG_MAX || t== LONG_MIN)) {
  208. LM_ERR("out of bounds\n");
  209. goto error;
  210. }
  211. n++;
  212. /* add rule -> has prefix? */
  213. if (prefix->len) {
  214. /* add the routing rule */
  215. if ( add_prefix(rdata->pt, prefix, rule, (unsigned int)t)!=0 ) {
  216. LM_ERR("failed to add prefix route\n");
  217. goto error;
  218. }
  219. } else {
  220. if ( add_rt_info( &rdata->noprefix, rule, (unsigned int)t)!=0 ) {
  221. LM_ERR("failed to add prefixless route\n");
  222. goto error;
  223. }
  224. }
  225. /* keep parsing */
  226. if(IS_SPACE(*ep))
  227. EAT_SPACE(ep);
  228. if(ep && (*ep == SEP || *ep == SEP1))
  229. ep++;
  230. tmp = ep;
  231. }
  232. if(n==0) {
  233. LM_ERR("no id in grp list [%s]\n",
  234. grplst);
  235. goto error;
  236. }
  237. return 0;
  238. error:
  239. return -1;
  240. }
  241. rt_data_t* dr_load_routing_info( db_func_t *dr_dbf, db1_con_t* db_hdl,
  242. str *drd_table, str *drl_table, str* drr_table )
  243. {
  244. int int_vals[4];
  245. char * str_vals[5];
  246. str tmp;
  247. db_key_t columns[7];
  248. db1_res_t* res;
  249. db_row_t* row;
  250. rt_info_t *ri;
  251. rt_data_t *rdata;
  252. tmrec_t *time_rec;
  253. unsigned int id;
  254. str s_id;
  255. int i,n;
  256. res = 0;
  257. ri = 0;
  258. rdata = 0;
  259. /* init new data structure */
  260. if ( (rdata=build_rt_data())==0 ) {
  261. LM_ERR("failed to build rdata\n");
  262. goto error;
  263. }
  264. /* read the destinations */
  265. if (dr_dbf->use_table( db_hdl, drd_table) < 0) {
  266. LM_ERR("cannot select table \"%.*s\"\n", drd_table->len,drd_table->s);
  267. goto error;
  268. }
  269. columns[0] = &dst_id_drd_col;
  270. columns[1] = &address_drd_col;
  271. columns[2] = &strip_drd_col;
  272. columns[3] = &prefix_drd_col;
  273. columns[4] = &type_drd_col;
  274. columns[5] = &attrs_drd_col;
  275. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  276. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 6, 0, 0 ) < 0) {
  277. LM_ERR("DB query failed\n");
  278. goto error;
  279. }
  280. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  281. LM_ERR("Error fetching rows\n");
  282. goto error;
  283. }
  284. } else {
  285. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 6, 0, &res) < 0) {
  286. LM_ERR("DB query failed\n");
  287. goto error;
  288. }
  289. }
  290. if (RES_ROW_N(res) == 0) {
  291. LM_WARN("table \"%.*s\" empty\n", drd_table->len,drd_table->s );
  292. }
  293. LM_DBG("%d records found in %.*s\n",
  294. RES_ROW_N(res), drd_table->len,drd_table->s);
  295. n = 0;
  296. do {
  297. for(i=0; i < RES_ROW_N(res); i++) {
  298. row = RES_ROWS(res) + i;
  299. /* DST_ID column */
  300. check_val( ROW_VALUES(row), DB1_INT, 1, 0);
  301. int_vals[0] = VAL_INT (ROW_VALUES(row));
  302. /* ADDRESS column */
  303. check_val( ROW_VALUES(row)+1, DB1_STRING, 1, 1);
  304. str_vals[0] = (char*)VAL_STRING(ROW_VALUES(row)+1);
  305. /* STRIP column */
  306. check_val( ROW_VALUES(row)+2, DB1_INT, 1, 0);
  307. int_vals[1] = VAL_INT (ROW_VALUES(row)+2);
  308. /* PREFIX column */
  309. check_val( ROW_VALUES(row)+3, DB1_STRING, 0, 0);
  310. str_vals[1] = (char*)VAL_STRING(ROW_VALUES(row)+3);
  311. /* TYPE column */
  312. check_val( ROW_VALUES(row)+4, DB1_INT, 1, 0);
  313. int_vals[2] = VAL_INT(ROW_VALUES(row)+4);
  314. /* ATTRS column */
  315. check_val( ROW_VALUES(row)+5, DB1_STRING, 0, 0);
  316. str_vals[2] = (char*)VAL_STRING(ROW_VALUES(row)+5);
  317. /* add the destinaton definition in */
  318. if ( add_dst( rdata, int_vals[0], str_vals[0], int_vals[1],
  319. str_vals[1], int_vals[2], str_vals[2])<0 ) {
  320. LM_ERR("failed to add destination id %d -> skipping\n",
  321. int_vals[0]);
  322. continue;
  323. }
  324. n++;
  325. }
  326. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  327. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  328. LM_ERR( "fetching rows (1)\n");
  329. goto error;
  330. }
  331. } else {
  332. break;
  333. }
  334. } while(RES_ROW_N(res)>0);
  335. dr_dbf->free_result(db_hdl, res);
  336. res = 0;
  337. if (n==0) {
  338. LM_WARN("no valid "
  339. "destinations set -> ignoring the routing rules\n");
  340. return rdata;
  341. }
  342. /* read the gw lists, if any */
  343. if (dr_dbf->use_table( db_hdl, drl_table) < 0) {
  344. LM_ERR("cannot select table \"%.*s\"\n", drl_table->len,drl_table->s);
  345. goto error;
  346. }
  347. columns[0] = &id_drl_col;
  348. columns[1] = &gwlist_drl_col;
  349. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  350. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 2, 0, 0 ) < 0) {
  351. LM_ERR("DB query failed\n");
  352. goto error;
  353. }
  354. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  355. LM_ERR("Error fetching rows\n");
  356. goto error;
  357. }
  358. } else {
  359. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 2, 0, &res) < 0) {
  360. LM_ERR("DB query failed\n");
  361. goto error;
  362. }
  363. }
  364. if (RES_ROW_N(res) == 0) {
  365. LM_DBG("table \"%.*s\" empty\n", drl_table->len,drl_table->s );
  366. } else {
  367. LM_DBG("%d records found in %.*s\n",
  368. RES_ROW_N(res), drl_table->len,drl_table->s);
  369. do {
  370. for(i=0; i < RES_ROW_N(res); i++) {
  371. row = RES_ROWS(res) + i;
  372. /* ID column */
  373. check_val( ROW_VALUES(row), DB1_INT, 1, 0);
  374. int_vals[0] = VAL_INT (ROW_VALUES(row));
  375. /* GWLIST column */
  376. check_val( ROW_VALUES(row)+1, DB1_STRING, 1, 1);
  377. str_vals[0] = (char*)VAL_STRING(ROW_VALUES(row)+1);
  378. if (add_tmp_gw_list(int_vals[0], str_vals[0])!=0) {
  379. LM_ERR("failed to add temporary GW list\n");
  380. goto error;
  381. }
  382. }
  383. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  384. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  385. LM_ERR( "fetching rows (1)\n");
  386. goto error;
  387. }
  388. } else {
  389. break;
  390. }
  391. } while(RES_ROW_N(res)>0);
  392. }
  393. dr_dbf->free_result(db_hdl, res);
  394. res = 0;
  395. /* read the routing rules */
  396. if (dr_dbf->use_table( db_hdl, drr_table) < 0) {
  397. LM_ERR("cannot select table \"%.*s\"\n", drr_table->len, drr_table->s);
  398. goto error;
  399. }
  400. columns[0] = &rule_id_drr_col;
  401. columns[1] = &group_drr_col;
  402. columns[2] = &prefix_drr_col;
  403. columns[3] = &time_drr_col;
  404. columns[4] = &priority_drr_col;
  405. columns[5] = &routeid_drr_col;
  406. columns[6] = &dstlist_drr_col;
  407. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  408. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 7, 0, 0) < 0) {
  409. LM_ERR("DB query failed\n");
  410. goto error;
  411. }
  412. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  413. LM_ERR("Error fetching rows\n");
  414. goto error;
  415. }
  416. } else {
  417. if ( dr_dbf->query( db_hdl, 0, 0, 0, columns, 0, 7, 0, &res) < 0) {
  418. LM_ERR("DB query failed\n");
  419. goto error;
  420. }
  421. }
  422. if (RES_ROW_N(res) == 0) {
  423. LM_WARN("table \"%.*s\" is empty\n", drr_table->len, drr_table->s);
  424. }
  425. LM_DBG("%d records found in %.*s\n", RES_ROW_N(res),
  426. drr_table->len, drr_table->s);
  427. n = 0;
  428. do {
  429. for(i=0; i < RES_ROW_N(res); i++) {
  430. row = RES_ROWS(res) + i;
  431. /* RULE_ID column */
  432. check_val( ROW_VALUES(row), DB1_INT, 1, 0);
  433. int_vals[0] = VAL_INT (ROW_VALUES(row));
  434. /* GROUP column */
  435. check_val( ROW_VALUES(row)+1, DB1_STRING, 1, 1);
  436. str_vals[0] = (char*)VAL_STRING(ROW_VALUES(row)+1);
  437. /* PREFIX column - it may be null or empty */
  438. check_val( ROW_VALUES(row)+2, DB1_STRING, 0, 0);
  439. if ((ROW_VALUES(row)+2)->nul || VAL_STRING(ROW_VALUES(row)+2)==0){
  440. tmp.s = NULL;
  441. tmp.len = 0;
  442. } else {
  443. str_vals[1] = (char*)VAL_STRING(ROW_VALUES(row)+2);
  444. tmp.s = str_vals[1];
  445. tmp.len = strlen(str_vals[1]);
  446. }
  447. /* TIME column */
  448. check_val( ROW_VALUES(row)+3, DB1_STRING, 1, 1);
  449. str_vals[2] = (char*)VAL_STRING(ROW_VALUES(row)+3);
  450. /* PRIORITY column */
  451. check_val( ROW_VALUES(row)+4, DB1_INT, 1, 0);
  452. int_vals[2] = VAL_INT (ROW_VALUES(row)+4);
  453. /* ROUTE_ID column */
  454. check_val( ROW_VALUES(row)+5, DB1_STRING, 1, 0);
  455. str_vals[3] = (char*)VAL_STRING(ROW_VALUES(row)+5);
  456. /* DSTLIST column */
  457. check_val( ROW_VALUES(row)+6, DB1_STRING, 1, 1);
  458. str_vals[4] = (char*)VAL_STRING(ROW_VALUES(row)+6);
  459. /* parse the time definition */
  460. if ((time_rec=parse_time_def(str_vals[2]))==0) {
  461. LM_ERR("bad time definition <%s> for rule id %d -> skipping\n",
  462. str_vals[2], int_vals[0]);
  463. continue;
  464. }
  465. /* lookup for the script route ID */
  466. if (str_vals[3][0] && str_vals[3][0]!='0') {
  467. int_vals[3] = route_lookup(&main_rt, str_vals[3]);
  468. if (int_vals[3]==-1) {
  469. LM_WARN("route <%s> does not exist\n",str_vals[3]);
  470. int_vals[3] = 0;
  471. }
  472. } else {
  473. int_vals[3] = 0;
  474. }
  475. /* is gw_list a list or a list id? */
  476. if (str_vals[4][0]=='#') {
  477. s_id.s = str_vals[4]+1;
  478. s_id.len = strlen(s_id.s);
  479. if ( str2int( &s_id, &id)!=0 ||
  480. (str_vals[4]=get_tmp_gw_list(id))==NULL ) {
  481. LM_ERR("invalid reference to a GW list <%s> -> skipping\n",
  482. str_vals[4]);
  483. continue;
  484. }
  485. }
  486. /* build the routing rule */
  487. if ((ri = build_rt_info( int_vals[2], time_rec, int_vals[3],
  488. str_vals[4], rdata->pgw_l))== 0 ) {
  489. LM_ERR("failed to add routing info for rule id %d -> "
  490. "skipping\n", int_vals[0]);
  491. tmrec_free( time_rec );
  492. continue;
  493. }
  494. /* add the rule */
  495. if (add_rule( rdata, str_vals[0], &tmp, ri)!=0) {
  496. LM_ERR("failed to add rule id %d -> skipping\n", int_vals[0]);
  497. free_rt_info( ri );
  498. continue;
  499. }
  500. n++;
  501. }
  502. if (DB_CAPABILITY(*dr_dbf, DB_CAP_FETCH)) {
  503. if(dr_dbf->fetch_result(db_hdl, &res, dr_fetch_rows)<0) {
  504. LM_ERR( "fetching rows (1)\n");
  505. goto error;
  506. }
  507. } else {
  508. break;
  509. }
  510. } while(RES_ROW_N(res)>0);
  511. dr_dbf->free_result(db_hdl, res);
  512. res = 0;
  513. free_tmp_gw_list();
  514. if (n==0) {
  515. LM_WARN("no valid routing rules -> discarding all destinations\n");
  516. free_rt_data( rdata, 0 );
  517. }
  518. return rdata;
  519. error:
  520. if (res)
  521. dr_dbf->free_result(db_hdl, res);
  522. if (rdata)
  523. free_rt_data( rdata, 1 );
  524. rdata = NULL;
  525. return 0;
  526. }