dbcassa_table.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. * $Id$
  3. *
  4. * Copyright (C) 2012 1&1 Internet Development
  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. * 2012-01 initial version (Anca Vamanu)
  25. *
  26. */
  27. #include <stdio.h>
  28. #include <string.h>
  29. #include <time.h>
  30. #include <sys/types.h>
  31. #include <sys/stat.h>
  32. #include <dirent.h>
  33. #include "../../mem/shm_mem.h"
  34. #include "../../mem/mem.h"
  35. #include "../../dprint.h"
  36. #include "../../hashes.h"
  37. #include "../../lock_ops.h"
  38. #include "dbcassa_table.h"
  39. #define DBCASSA_TABLE_SIZE 16
  40. typedef struct rw_lock {
  41. gen_lock_t lock;
  42. int reload_flag;
  43. int data_refcnt;
  44. } rw_lock_t;
  45. typedef struct _dbcassa_tbl_htable
  46. {
  47. rw_lock_t lock;
  48. dbcassa_table_p dtp;
  49. } dbcassa_tbl_htable_t, *dbcassa_tbl_htable_p;
  50. static dbcassa_tbl_htable_p dbcassa_tbl_htable = NULL;
  51. extern str dbcassa_schema_path;
  52. static char full_path_buf[_POSIX_PATH_MAX + 1];
  53. /**
  54. * Check if file modified from last read
  55. * -1 - error
  56. * 0 - no change
  57. * 1 - changed
  58. */
  59. int dbcassa_check_mtime(time_t *mt)
  60. {
  61. struct stat s;
  62. if(stat(full_path_buf, &s) == 0)
  63. {
  64. if((int)s.st_mtime > (int)*mt)
  65. {
  66. *mt = s.st_mtime;
  67. LM_DBG("[%s] was updated\n", full_path_buf);
  68. return 1;
  69. }
  70. } else {
  71. LM_DBG("stat failed on [%s]\n", full_path_buf);
  72. return -1;
  73. }
  74. return 0;
  75. }
  76. /*
  77. * Create new table structure
  78. *
  79. * */
  80. dbcassa_table_p dbcassa_table_new(const str *_tbname, const str *_dbname)
  81. {
  82. struct stat s;
  83. dbcassa_table_p dtp = NULL;
  84. int size;
  85. if(!_tbname || !_dbname) {
  86. LM_ERR("Invalid parameters\n");
  87. return 0;
  88. }
  89. size = sizeof(dbcassa_table_t)+_tbname->len+_dbname->len;
  90. dtp = (dbcassa_table_p)shm_malloc(size);
  91. if(!dtp) {
  92. LM_ERR("No more shared memory\n");
  93. return 0;
  94. }
  95. memset(dtp, 0, size);
  96. size = sizeof(dbcassa_table_t);
  97. dtp->name.s = (char*)dtp + size;
  98. memcpy(dtp->name.s, _tbname->s, _tbname->len);
  99. dtp->name.len = _tbname->len;
  100. size+= _tbname->len;
  101. dtp->dbname.s = (char*)dtp + size;
  102. memcpy(dtp->dbname.s, _dbname->s, _dbname->len);
  103. dtp->dbname.len = _dbname->len;
  104. if(stat(full_path_buf, &s) == 0) {
  105. dtp->mt = s.st_mtime;
  106. LM_DBG("mtime is %d\n", (int)s.st_mtime);
  107. }
  108. return dtp;
  109. }
  110. dbcassa_column_p dbcassa_column_new(char *_s, int _l)
  111. {
  112. dbcassa_column_p dcp;
  113. int size;
  114. size = sizeof(dbcassa_column_t) + _l+ 1;
  115. dcp = (dbcassa_column_p)shm_malloc(size);
  116. if(!dcp) {
  117. LM_ERR("No more shared memory\n");
  118. return 0;
  119. }
  120. memset(dcp, 0, size);
  121. dcp->name.s = (char*)dcp + sizeof(dbcassa_column_t);
  122. memcpy(dcp->name.s, _s, _l);
  123. dcp->name.len = _l;
  124. dcp->name.s[_l] = '\0';
  125. return dcp;
  126. }
  127. int dbcassa_column_free(dbcassa_column_p dcp)
  128. {
  129. if(!dcp)
  130. return -1;
  131. shm_free(dcp);
  132. return 0;
  133. }
  134. int dbcassa_table_free(dbcassa_table_p _dtp)
  135. {
  136. dbcassa_column_p _cp, _cp0;
  137. if(!_dtp)
  138. return -1;
  139. /* cols*/
  140. _cp = _dtp->cols;
  141. while(_cp) {
  142. _cp0=_cp;
  143. _cp=_cp->next;
  144. dbcassa_column_free(_cp0);
  145. }
  146. /* key */
  147. if(_dtp->key)
  148. shm_free(_dtp->key);
  149. if(_dtp->sec_key)
  150. shm_free(_dtp->sec_key);
  151. shm_free(_dtp);
  152. return 0;
  153. }
  154. /**
  155. * Load the table schema from file
  156. */
  157. dbcassa_table_p dbcassa_load_file(str* dbn, str* tbn)
  158. {
  159. #define KEY_MAX_LEN 10
  160. FILE *fin=NULL;
  161. char buf[4096];
  162. int c, crow, ccol, bp, max_auto;
  163. dbcassa_table_p dtp = 0;
  164. dbcassa_column_p colp= 0;
  165. dbcassa_column_p key[KEY_MAX_LEN];
  166. dbcassa_column_p sec_key[KEY_MAX_LEN];
  167. enum {DBCASSA_FLINE_ST, DBCASSA_NLINE_ST, DBCASSA_NLINE2_ST} state;
  168. memset(key, 0, KEY_MAX_LEN*sizeof(dbcassa_column_p));
  169. memset(sec_key, 0, KEY_MAX_LEN*sizeof(dbcassa_column_p));
  170. LM_DBG("loading file [%s]\n", full_path_buf);
  171. fin = fopen(full_path_buf, "rt");
  172. if(!fin) {
  173. LM_ERR("Failed to open file\n");
  174. return 0;
  175. }
  176. dtp = dbcassa_table_new(tbn, dbn);
  177. if(!dtp)
  178. goto done;
  179. state = DBCASSA_FLINE_ST;
  180. crow = ccol = -1;
  181. c = fgetc(fin);
  182. max_auto = 0;
  183. while(c!=EOF) {
  184. switch(state) {
  185. case DBCASSA_FLINE_ST:
  186. bp = 0;
  187. while(c==DBCASSA_DELIM_C)
  188. c = fgetc(fin);
  189. if(c==DBCASSA_DELIM_R && !dtp->cols)
  190. goto clean;
  191. if(c==DBCASSA_DELIM_R) {
  192. if(dtp->nrcols <= 0)
  193. goto clean;
  194. state = DBCASSA_NLINE_ST;
  195. c = fgetc(fin);
  196. break;
  197. }
  198. while(c!=DBCASSA_DELIM_C && c!='(' && c!=DBCASSA_DELIM_R) {
  199. if(c==EOF)
  200. goto clean;
  201. buf[bp++] = c;
  202. c = fgetc(fin);
  203. }
  204. colp = dbcassa_column_new(buf, bp);
  205. if(!colp)
  206. goto clean;
  207. LM_DBG("new col [%.*s]\n", bp, buf);
  208. while(c==DBCASSA_DELIM_C)
  209. c = fgetc(fin);
  210. if(c!='(')
  211. goto clean;
  212. c = fgetc(fin);
  213. while(c==DBCASSA_DELIM_C)
  214. c = fgetc(fin);
  215. switch(c) {
  216. case 's':
  217. case 'S':
  218. colp->type = DB1_STR;
  219. LM_DBG("column[%d] is STR!\n", ccol+1);
  220. break;
  221. case 'i':
  222. case 'I':
  223. colp->type = DB1_INT;
  224. LM_DBG("column[%d] is INT!\n", ccol+1);
  225. break;
  226. case 'd':
  227. case 'D':
  228. colp->type = DB1_DOUBLE;
  229. LM_DBG("column[%d] is DOUBLE!\n", ccol+1);
  230. break;
  231. case 't':
  232. case 'T':
  233. colp->type = DB1_DATETIME;
  234. LM_DBG("column[%d] is TIME! Timestamp col has name [%s]\n", ccol+1, colp->name.s);
  235. if(dtp->ts_col) {
  236. LM_ERR("You can have only one column with type timestamp\n");
  237. goto clean;
  238. }
  239. dtp->ts_col = colp;
  240. break;
  241. default:
  242. LM_DBG("wrong column type!\n");
  243. goto clean;
  244. }
  245. while(c!='\n' && c!=EOF && c!=')' && c!= ',') {
  246. if(colp->type == DB1_STR && (c=='i'|| c=='I')) {
  247. colp->type = DB1_STRING;
  248. LM_DBG("column[%d] is actually STRING!\n", ccol+1);
  249. }
  250. c = fgetc(fin);
  251. }
  252. if(c == ')') {
  253. //LM_DBG("c=%c!\n", c);
  254. colp->next = dtp->cols;
  255. dtp->cols = colp;
  256. dtp->nrcols++;
  257. c = fgetc(fin);
  258. }
  259. else
  260. goto clean;
  261. ccol++;
  262. break;
  263. case DBCASSA_NLINE_ST:
  264. case DBCASSA_NLINE2_ST:
  265. // unique key
  266. while(c==DBCASSA_DELIM_C)
  267. c = fgetc(fin);
  268. if(c == DBCASSA_DELIM_R) {
  269. state = DBCASSA_NLINE2_ST;
  270. c = fgetc(fin);
  271. break;
  272. }
  273. if(c == EOF)
  274. break;
  275. bp= 0;
  276. while(c!=DBCASSA_DELIM_C && c!=DBCASSA_DELIM_R)
  277. {
  278. if(c==EOF)
  279. break;
  280. buf[bp++] = c;
  281. c = fgetc(fin);
  282. }
  283. colp = dtp->cols;
  284. while(colp) {
  285. if(bp==colp->name.len && strncmp(colp->name.s, buf, bp)==0) {
  286. if(state == DBCASSA_NLINE_ST)
  287. key[dtp->key_len++] = colp;
  288. else
  289. sec_key[dtp->seckey_len++] = colp;
  290. break;
  291. }
  292. colp = colp->next;
  293. }
  294. if(!colp) {
  295. LM_ERR("Undefined column in key [%.*s]\n", bp, buf);
  296. goto clean;
  297. }
  298. break;
  299. }
  300. }
  301. /* copy the keys into the table */
  302. if(dtp->key_len) {
  303. dtp->key = (dbcassa_column_p*)
  304. shm_malloc(dtp->key_len*sizeof(dbcassa_column_p));
  305. if(!dtp->key) {
  306. LM_ERR("No more share memory\n");
  307. goto clean;
  308. }
  309. for(ccol = 0; ccol< dtp->key_len; ccol++) {
  310. dtp->key[ccol] = key[ccol];
  311. LM_DBG("col [%.*s] in primary key\n", key[ccol]->name.len, key[ccol]->name.s);
  312. }
  313. }
  314. if(dtp->seckey_len) {
  315. dtp->sec_key = (dbcassa_column_p*)
  316. shm_malloc(dtp->seckey_len*sizeof(dbcassa_column_p));
  317. if(!dtp->sec_key) {
  318. LM_ERR("No more share memory\n");
  319. goto clean;
  320. }
  321. for(ccol = 0; ccol< dtp->seckey_len; ccol++) {
  322. dtp->sec_key[ccol] = sec_key[ccol];
  323. LM_DBG("col [%.*s] in secondary key\n", sec_key[ccol]->name.len, sec_key[ccol]->name.s);
  324. }
  325. }
  326. done:
  327. if(fin)
  328. fclose(fin);
  329. return dtp;
  330. clean:
  331. if(fin)
  332. fclose(fin);
  333. if(dtp)
  334. dbcassa_table_free(dtp);
  335. return NULL;
  336. }
  337. #define ref_read_data(rw_lock) \
  338. do {\
  339. again:\
  340. lock_get( &rw_lock.lock ); \
  341. if (rw_lock.reload_flag) { \
  342. lock_release( &rw_lock.lock ); \
  343. usleep(5); \
  344. goto again; \
  345. } \
  346. rw_lock.data_refcnt++; \
  347. lock_release( &rw_lock.lock ); \
  348. } while(0)
  349. #define unref_read_data(rw_lock) \
  350. do {\
  351. lock_get( &rw_lock.lock ); \
  352. rw_lock.data_refcnt--; \
  353. lock_release( &rw_lock.lock ); \
  354. } while(0)
  355. #define ref_write_data(rw_lock)\
  356. do {\
  357. lock_get( &rw_lock.lock ); \
  358. rw_lock.reload_flag = 1; \
  359. lock_release( &rw_lock.lock ); \
  360. while (rw_lock.data_refcnt) \
  361. usleep(10); \
  362. } while(0)
  363. #define unref_write_data(rw_lock)\
  364. rw_lock.reload_flag = 0;
  365. /*
  366. * Search the table schema
  367. * */
  368. dbcassa_table_p dbcassa_db_search_table(int hashidx, int hash,
  369. const str* dbn, const str *tbn)
  370. {
  371. dbcassa_table_p tbc = NULL;
  372. ref_read_data(dbcassa_tbl_htable[hashidx].lock);
  373. tbc = dbcassa_tbl_htable[hashidx].dtp;
  374. while(tbc) {
  375. LM_DBG("found dbname=%.*s, table=%.*s\n", tbc->dbname.len, tbc->dbname.s, tbc->name.len, tbc->name.s);
  376. if(tbc->hash==hash && tbc->dbname.len == dbn->len
  377. && tbc->name.len == tbn->len
  378. && !strncasecmp(tbc->dbname.s, dbn->s, dbn->len)
  379. && !strncasecmp(tbc->name.s, tbn->s, tbn->len))
  380. return tbc;
  381. tbc = tbc->next;
  382. }
  383. unref_read_data(dbcassa_tbl_htable[hashidx].lock);
  384. return NULL;
  385. }
  386. /**
  387. * Get the table schema. If the file was updated, update the table schema.
  388. */
  389. dbcassa_table_p dbcassa_db_get_table(const str* dbn, const str *tbn)
  390. {
  391. dbcassa_table_p tbc = NULL, old_tbc= NULL, new_tbc= NULL, prev_tbc= NULL;
  392. int hash;
  393. int hashidx;
  394. int len;
  395. if(!dbn || !tbn ) {
  396. LM_ERR("invalid parameter");
  397. return NULL;
  398. }
  399. hash = core_hash(dbn, tbn, DBCASSA_TABLE_SIZE);
  400. hashidx = hash % DBCASSA_TABLE_SIZE;
  401. ref_read_data(dbcassa_tbl_htable[hashidx].lock);
  402. tbc = dbcassa_tbl_htable[hashidx].dtp;
  403. while(tbc) {
  404. LM_DBG("found dbname=%.*s, table=%.*s\n", tbc->dbname.len, tbc->dbname.s, tbc->name.len, tbc->name.s);
  405. if(tbc->hash==hash && tbc->dbname.len == dbn->len
  406. && tbc->name.len == tbn->len
  407. && !strncasecmp(tbc->dbname.s, dbn->s, dbn->len)
  408. && !strncasecmp(tbc->name.s, tbn->s, tbn->len)) {
  409. memcpy(full_path_buf + dbcassa_schema_path.len, dbn->s, dbn->len);
  410. len = dbcassa_schema_path.len + dbn->len;
  411. full_path_buf[len++] = '/';
  412. memcpy(full_path_buf + len, tbn->s, tbn->len);
  413. full_path_buf[len + tbn->len] = '\0';
  414. if(dbcassa_check_mtime(&tbc->mt) == 0)
  415. return tbc;
  416. old_tbc = tbc;
  417. break;
  418. }
  419. tbc = tbc->next;
  420. }
  421. unref_read_data(dbcassa_tbl_htable[hashidx].lock);
  422. if(!old_tbc)
  423. return NULL;
  424. /* the file has changed - load again the schema */
  425. new_tbc = dbcassa_load_file((str*)dbn, (str*)tbn);
  426. if(!new_tbc)
  427. {
  428. LM_ERR("could not load database from file [%.*s]\n", tbn->len, tbn->s);
  429. return NULL;
  430. }
  431. new_tbc->hash = hashidx;
  432. /* lock for write */
  433. ref_write_data(dbcassa_tbl_htable[hashidx].lock);
  434. tbc = dbcassa_tbl_htable[hashidx].dtp;
  435. while(tbc) {
  436. if(tbc == old_tbc)
  437. break;
  438. prev_tbc = tbc;
  439. tbc = tbc->next;
  440. }
  441. /* somebody else might have rewritten it in the mean time? just return the existing one */
  442. if(!tbc) {
  443. unref_write_data(dbcassa_tbl_htable[hashidx].lock);
  444. return dbcassa_db_search_table(hashidx, hash, dbn, tbn);
  445. }
  446. /* replace the table */
  447. new_tbc->next = old_tbc->next;
  448. if(prev_tbc)
  449. prev_tbc->next = new_tbc;
  450. else
  451. dbcassa_tbl_htable[hashidx].dtp = new_tbc;
  452. dbcassa_table_free(old_tbc);
  453. unref_write_data(dbcassa_tbl_htable[hashidx].lock);
  454. /* lock for read, search the table and return */
  455. return dbcassa_db_search_table(hashidx, hash, dbn, tbn);
  456. }
  457. /*
  458. * Read all table schemas at startup
  459. * */
  460. int dbcassa_read_table_schemas(void)
  461. {
  462. int i, j;
  463. str db_name, tb_name;
  464. DIR* srcdir = opendir(dbcassa_schema_path.s);
  465. DIR* db_dir;
  466. struct dirent* dent;
  467. int fn_len = dbcassa_schema_path.len;
  468. struct stat fstat;
  469. int dir_len;
  470. dbcassa_table_p tbc;
  471. unsigned int hashidx;
  472. /* init tables' hash table */
  473. if (!dbcassa_tbl_htable) {
  474. dbcassa_tbl_htable = (dbcassa_tbl_htable_p)shm_malloc(DBCASSA_TABLE_SIZE*
  475. sizeof(dbcassa_tbl_htable_t));
  476. if(dbcassa_tbl_htable==NULL)
  477. {
  478. LM_CRIT("no enough shm mem\n");
  479. return -1;
  480. }
  481. memset(dbcassa_tbl_htable, 0, DBCASSA_TABLE_SIZE*sizeof(dbcassa_tbl_htable_t));
  482. for(i=0; i<DBCASSA_TABLE_SIZE; i++)
  483. {
  484. if (lock_init(&dbcassa_tbl_htable[i].lock.lock)==0)
  485. {
  486. LM_CRIT("cannot init tables' sem's\n");
  487. for(j=i-1; j>=0; j--)
  488. lock_destroy(&dbcassa_tbl_htable[j].rw_lock.lock);
  489. return -1;
  490. }
  491. }
  492. }
  493. memset(full_path_buf, 0, _POSIX_PATH_MAX);
  494. strcpy(full_path_buf, dbcassa_schema_path.s);
  495. if (full_path_buf[dbcassa_schema_path.len - 1] != '/') {
  496. full_path_buf[fn_len++]= '/';
  497. dbcassa_schema_path.len++;
  498. }
  499. if (srcdir == NULL) {
  500. perror("opendir");
  501. return -1;
  502. }
  503. LM_DBG("Full name= %.*s\n", fn_len, full_path_buf);
  504. while((dent = readdir(srcdir)) != NULL)
  505. {
  506. if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
  507. continue;
  508. /* Calculate full name, check we are in file length limits */
  509. if ((fn_len + strlen(dent->d_name) + 1) > _POSIX_PATH_MAX)
  510. continue;
  511. db_name.s = dent->d_name;
  512. db_name.len = strlen(dent->d_name);
  513. strcpy(full_path_buf+fn_len, dent->d_name);
  514. dir_len = fn_len + db_name.len;
  515. LM_DBG("Full dir name= %.*s\n", dir_len, full_path_buf);
  516. if (stat(full_path_buf, &fstat) < 0) {
  517. LM_ERR("stat failed %s\n", strerror(errno));
  518. continue;
  519. }
  520. if (!S_ISDIR(fstat.st_mode)) {
  521. LM_ERR("not a directory\n");
  522. continue;
  523. }
  524. /*
  525. if (fstatat(dirfd(srcdir), dent->d_name, &st) < 0)
  526. {
  527. perror(dent->d_name);
  528. continue;
  529. }
  530. */
  531. LM_DBG("Found database %s\n", dent->d_name);
  532. db_dir = opendir(full_path_buf);
  533. if(!db_dir) {
  534. LM_ERR("Failed to open dictory %s\n", full_path_buf);
  535. continue;
  536. }
  537. full_path_buf[dir_len++]= '/';
  538. while((dent = readdir(db_dir)) != NULL)
  539. {
  540. if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0)
  541. continue;
  542. LM_DBG("database table %s\n", dent->d_name);
  543. if(dir_len + strlen(dent->d_name)+1 > _POSIX_PATH_MAX) {
  544. LM_ERR("File len too large\n");
  545. continue;
  546. }
  547. strcpy(full_path_buf+dir_len, dent->d_name);
  548. tb_name.s = dent->d_name;
  549. tb_name.len = strlen(dent->d_name);
  550. LM_DBG("File path= %s\n", full_path_buf);
  551. tbc = dbcassa_load_file(&db_name, &tb_name);
  552. if(!tbc)
  553. {
  554. LM_ERR("could not load database from file [%s]\n", tb_name.s);
  555. return -1;
  556. }
  557. hashidx = core_hash(&db_name, &tb_name, DBCASSA_TABLE_SIZE);
  558. tbc->hash = hashidx;
  559. tbc->next = dbcassa_tbl_htable[hashidx].dtp;
  560. dbcassa_tbl_htable[hashidx].dtp = tbc;
  561. }
  562. closedir(db_dir);
  563. }
  564. closedir(srcdir);
  565. return 0;
  566. }
  567. /*
  568. * Destroy table schema table at shutdown
  569. * */
  570. void dbcassa_destroy_htable(void)
  571. {
  572. int i;
  573. dbcassa_table_p tbc, tbc0;
  574. /* destroy tables' hash table*/
  575. if(dbcassa_tbl_htable==0)
  576. return;
  577. for(i=0; i<DBCASSA_TABLE_SIZE; i++) {
  578. lock_destroy(&dbcassa_tbl_htable[i].rw_lock.lock);
  579. tbc = dbcassa_tbl_htable[i].dtp;
  580. while(tbc) {
  581. tbc0 = tbc;
  582. tbc = tbc->next;
  583. dbcassa_table_free(tbc0);
  584. }
  585. }
  586. shm_free(dbcassa_tbl_htable);
  587. }
  588. void dbcassa_lock_release(dbcassa_table_p tbc)
  589. {
  590. unref_read_data(dbcassa_tbl_htable[tbc->hash].lock);
  591. }