corosync-objctl.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. /*
  2. * Copyright (c) 2008 Allied Telesis Labs NZ
  3. *
  4. * All rights reserved.
  5. *
  6. * Author: Angus Salkeld <angus.salkeld@alliedtelesis.co.nz>
  7. *
  8. * This software licensed under BSD license, the text of which follows:
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions are met:
  12. *
  13. * - Redistributions of source code must retain the above copyright notice,
  14. * this list of conditions and the following disclaimer.
  15. * - Redistributions in binary form must reproduce the above copyright notice,
  16. * this list of conditions and the following disclaimer in the documentation
  17. * and/or other materials provided with the distribution.
  18. * - Neither the name of the MontaVista Software, Inc. nor the names of its
  19. * contributors may be used to endorse or promote products derived from this
  20. * software without specific prior written permission.
  21. *
  22. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  23. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  24. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  25. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  26. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  27. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  28. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  29. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  30. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  31. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  32. * THE POSSIBILITY OF SUCH DAMAGE.
  33. */
  34. #include <sys/select.h>
  35. #include <stdio.h>
  36. #include <stdlib.h>
  37. #include <errno.h>
  38. #include <signal.h>
  39. #include <unistd.h>
  40. #include <string.h>
  41. #include <sys/types.h>
  42. #include <sys/un.h>
  43. #include <corosync/corotypes.h>
  44. #include <corosync/confdb.h>
  45. #define SEPERATOR '.'
  46. #define SEPERATOR_STR "."
  47. #define OBJ_NAME_SIZE 512
  48. typedef enum {
  49. ACTION_READ,
  50. ACTION_WRITE,
  51. ACTION_CREATE,
  52. ACTION_DELETE,
  53. ACTION_PRINT_ALL,
  54. ACTION_PRINT_DEFAULT,
  55. ACTION_TRACK,
  56. } action_types_t;
  57. typedef enum {
  58. FIND_OBJECT_ONLY,
  59. FIND_OBJECT_OR_KEY,
  60. FIND_KEY_ONLY
  61. } find_object_of_type_t;
  62. static void tail_key_changed(confdb_handle_t handle,
  63. confdb_change_type_t change_type,
  64. hdb_handle_t parent_object_handle,
  65. hdb_handle_t object_handle,
  66. void *object_name,
  67. int object_name_len,
  68. void *key_name,
  69. int key_name_len,
  70. void *key_value,
  71. int key_value_len);
  72. static void tail_object_created(confdb_handle_t handle,
  73. hdb_handle_t parent_object_handle,
  74. hdb_handle_t object_handle,
  75. uint8_t *name_pt,
  76. int name_len);
  77. static void tail_object_deleted(confdb_handle_t handle,
  78. hdb_handle_t parent_object_handle,
  79. uint8_t *name_pt,
  80. int name_len);
  81. confdb_callbacks_t callbacks = {
  82. .confdb_key_change_notify_fn = tail_key_changed,
  83. .confdb_object_create_change_notify_fn = tail_object_created,
  84. .confdb_object_delete_change_notify_fn = tail_object_deleted,
  85. };
  86. static int action;
  87. /* Recursively dump the object tree */
  88. static void print_config_tree(confdb_handle_t handle, hdb_handle_t parent_object_handle, char * parent_name)
  89. {
  90. hdb_handle_t object_handle;
  91. char object_name[OBJ_NAME_SIZE];
  92. int object_name_len;
  93. char key_name[OBJ_NAME_SIZE];
  94. int key_name_len;
  95. char key_value[OBJ_NAME_SIZE];
  96. int key_value_len;
  97. cs_error_t res;
  98. int children_printed;
  99. /* Show the keys */
  100. res = confdb_key_iter_start(handle, parent_object_handle);
  101. if (res != CS_OK) {
  102. fprintf(stderr, "error resetting key iterator for object %llu: %d\n", parent_object_handle, res);
  103. exit(EXIT_FAILURE);
  104. }
  105. children_printed = 0;
  106. while ( (res = confdb_key_iter(handle,
  107. parent_object_handle,
  108. key_name,
  109. &key_name_len,
  110. key_value,
  111. &key_value_len)) == CS_OK) {
  112. key_name[key_name_len] = '\0';
  113. key_value[key_value_len] = '\0';
  114. if (parent_name != NULL)
  115. printf("%s%c%s=%s\n", parent_name, SEPERATOR,key_name, key_value);
  116. else
  117. printf("%s=%s\n", key_name, key_value);
  118. children_printed++;
  119. }
  120. /* Show sub-objects */
  121. res = confdb_object_iter_start(handle, parent_object_handle);
  122. if (res != CS_OK) {
  123. fprintf(stderr, "error resetting object iterator for object %llu: %d\n", parent_object_handle, res);
  124. exit(EXIT_FAILURE);
  125. }
  126. while ( (res = confdb_object_iter(handle,
  127. parent_object_handle,
  128. &object_handle,
  129. object_name,
  130. &object_name_len)) == CS_OK) {
  131. object_name[object_name_len] = '\0';
  132. if (parent_name != NULL) {
  133. snprintf(key_value, OBJ_NAME_SIZE, "%s%c%s", parent_name, SEPERATOR, object_name);
  134. } else {
  135. if ((action == ACTION_PRINT_DEFAULT) && strcmp(object_name, "internal_configuration") == 0) continue;
  136. snprintf(key_value, OBJ_NAME_SIZE, "%s", object_name);
  137. }
  138. print_config_tree(handle, object_handle, key_value);
  139. children_printed++;
  140. }
  141. if (children_printed == 0 && parent_name != NULL) {
  142. printf("%s\n", parent_name);
  143. }
  144. }
  145. static int print_all(void)
  146. {
  147. confdb_handle_t handle;
  148. int result;
  149. result = confdb_initialize (&handle, &callbacks);
  150. if (result != CS_OK) {
  151. fprintf (stderr, "Could not initialize objdb library. Error %d\n", result);
  152. return 1;
  153. }
  154. print_config_tree(handle, OBJECT_PARENT_HANDLE, NULL);
  155. result = confdb_finalize (handle);
  156. return 0;
  157. }
  158. static int print_help(void)
  159. {
  160. printf("\n");
  161. printf ("usage: corosync-objctl object%ckey ... Print an object\n", SEPERATOR);
  162. printf (" corosync-objctl -c object%cchild_obj ... Create Object\n", SEPERATOR);
  163. printf (" corosync-objctl -d object%cchild_obj ... Delete object\n", SEPERATOR);
  164. printf (" corosync-objctl -w object%cchild_obj.key=value ... Create a key\n", SEPERATOR);
  165. printf (" corosync-objctl -t object%cchild_obj ... Track changes\n", SEPERATOR);
  166. printf (" corosync-objctl -a Print all objects\n");
  167. printf("\n");
  168. return 0;
  169. }
  170. static cs_error_t validate_name(char * obj_name_pt)
  171. {
  172. if ((strchr (obj_name_pt, SEPERATOR) == NULL) &&
  173. (strchr (obj_name_pt, '=') == NULL))
  174. return CS_OK;
  175. else
  176. return CS_ERR_INVALID_PARAM;
  177. }
  178. void get_child_name(char * name_pt, char * child_name)
  179. {
  180. char * tmp;
  181. char str_copy[OBJ_NAME_SIZE];
  182. strcpy(str_copy, name_pt);
  183. /* first remove the value (it could be a file path */
  184. tmp = strchr(str_copy, '=');
  185. if (tmp != NULL) *tmp = '\0';
  186. /* truncate the */
  187. tmp = strrchr(str_copy, SEPERATOR);
  188. if (tmp == NULL) tmp = str_copy;
  189. strcpy(child_name, tmp+1);
  190. }
  191. void get_parent_name(char * name_pt, char * parent_name)
  192. {
  193. char * tmp;
  194. strcpy(parent_name, name_pt);
  195. /* first remove the value (it could be a file path */
  196. tmp = strchr(parent_name, '=');
  197. if (tmp != NULL) *tmp = '\0';
  198. /* then truncate the child name */
  199. tmp = strrchr(parent_name, SEPERATOR);
  200. if (tmp != NULL) *tmp = '\0';
  201. }
  202. void get_key(char * name_pt, char * key_name, char * key_value)
  203. {
  204. char * tmp;
  205. char str_copy[OBJ_NAME_SIZE];
  206. strcpy(str_copy, name_pt);
  207. /* first remove the value (it could have a SEPERATOR in it */
  208. tmp = strchr(str_copy, '=');
  209. if (tmp != NULL && strlen(tmp) > 0) {
  210. strcpy(key_value, tmp+1);
  211. *tmp = '\0';
  212. } else {
  213. key_value[0] = '\0';
  214. }
  215. /* then remove the name */
  216. tmp = strrchr(str_copy, SEPERATOR);
  217. if (tmp == NULL) tmp = str_copy;
  218. strcpy(key_name, tmp+1);
  219. }
  220. static cs_error_t find_object (confdb_handle_t handle,
  221. char * name_pt,
  222. find_object_of_type_t type,
  223. hdb_handle_t * out_handle)
  224. {
  225. char * obj_name_pt;
  226. char * save_pt;
  227. hdb_handle_t obj_handle;
  228. confdb_handle_t parent_object_handle = OBJECT_PARENT_HANDLE;
  229. char tmp_name[OBJ_NAME_SIZE];
  230. cs_error_t res;
  231. strncpy (tmp_name, name_pt, OBJ_NAME_SIZE);
  232. obj_name_pt = strtok_r(tmp_name, SEPERATOR_STR, &save_pt);
  233. while (obj_name_pt != NULL) {
  234. res = confdb_object_find_start(handle, parent_object_handle);
  235. if (res != CS_OK) {
  236. fprintf (stderr, "Could not start object_find %d\n", res);
  237. exit (EXIT_FAILURE);
  238. }
  239. res = confdb_object_find(handle, parent_object_handle,
  240. obj_name_pt, strlen (obj_name_pt), &obj_handle);
  241. if (res != CS_OK) {
  242. return res;
  243. }
  244. parent_object_handle = obj_handle;
  245. obj_name_pt = strtok_r (NULL, SEPERATOR_STR, &save_pt);
  246. }
  247. *out_handle = parent_object_handle;
  248. return res;
  249. }
  250. static void read_object(confdb_handle_t handle, char * name_pt)
  251. {
  252. char parent_name[OBJ_NAME_SIZE];
  253. hdb_handle_t obj_handle;
  254. cs_error_t res;
  255. get_parent_name(name_pt, parent_name);
  256. res = find_object (handle, name_pt, FIND_OBJECT_OR_KEY, &obj_handle);
  257. if (res == CS_OK) {
  258. print_config_tree(handle, obj_handle, parent_name);
  259. }
  260. }
  261. static void write_key(confdb_handle_t handle, char * path_pt)
  262. {
  263. hdb_handle_t obj_handle;
  264. char parent_name[OBJ_NAME_SIZE];
  265. char key_name[OBJ_NAME_SIZE];
  266. char key_value[OBJ_NAME_SIZE];
  267. char old_key_value[OBJ_NAME_SIZE];
  268. int old_key_value_len;
  269. cs_error_t res;
  270. /* find the parent object */
  271. get_parent_name(path_pt, parent_name);
  272. get_key(path_pt, key_name, key_value);
  273. if (validate_name(key_name) != CS_OK) {
  274. fprintf(stderr, "Incorrect key name, can not have \"=\" or \"%c\"\n", SEPERATOR);
  275. exit(EXIT_FAILURE);
  276. }
  277. res = find_object (handle, parent_name, FIND_OBJECT_ONLY, &obj_handle);
  278. if (res != CS_OK) {
  279. fprintf(stderr, "Can't find parent object of \"%s\"\n", path_pt);
  280. exit(EXIT_FAILURE);
  281. }
  282. /* get the current key */
  283. res = confdb_key_get (handle,
  284. obj_handle,
  285. key_name,
  286. strlen(key_name),
  287. old_key_value,
  288. &old_key_value_len);
  289. if (res == CS_OK) {
  290. /* replace the current value */
  291. res = confdb_key_replace (handle,
  292. obj_handle,
  293. key_name,
  294. strlen(key_name),
  295. old_key_value,
  296. old_key_value_len,
  297. key_value,
  298. strlen(key_value));
  299. if (res != CS_OK)
  300. fprintf(stderr, "Failed to replace the key %s=%s. Error %d\n", key_name, key_value, res);
  301. } else {
  302. /* not there, create a new key */
  303. res = confdb_key_create (handle,
  304. obj_handle,
  305. key_name,
  306. strlen(key_name),
  307. key_value,
  308. strlen(key_value));
  309. if (res != CS_OK)
  310. fprintf(stderr, "Failed to create the key %s=%s. Error %d\n", key_name, key_value, res);
  311. }
  312. }
  313. static void create_object(confdb_handle_t handle, char * name_pt)
  314. {
  315. char * obj_name_pt;
  316. char * save_pt;
  317. hdb_handle_t obj_handle;
  318. hdb_handle_t parent_object_handle = OBJECT_PARENT_HANDLE;
  319. char tmp_name[OBJ_NAME_SIZE];
  320. cs_error_t res;
  321. strncpy (tmp_name, name_pt, OBJ_NAME_SIZE);
  322. obj_name_pt = strtok_r(tmp_name, SEPERATOR_STR, &save_pt);
  323. while (obj_name_pt != NULL) {
  324. res = confdb_object_find_start(handle, parent_object_handle);
  325. if (res != CS_OK) {
  326. fprintf (stderr, "Could not start object_find %d\n", res);
  327. exit (EXIT_FAILURE);
  328. }
  329. res = confdb_object_find(handle, parent_object_handle,
  330. obj_name_pt, strlen (obj_name_pt), &obj_handle);
  331. if (res != CS_OK) {
  332. if (validate_name(obj_name_pt) != CS_OK) {
  333. fprintf(stderr, "Incorrect object name \"%s\", \"=\" not allowed.\n",
  334. obj_name_pt);
  335. exit(EXIT_FAILURE);
  336. }
  337. res = confdb_object_create (handle,
  338. parent_object_handle,
  339. obj_name_pt,
  340. strlen (obj_name_pt),
  341. &obj_handle);
  342. if (res != CS_OK)
  343. fprintf(stderr, "Failed to create object \"%s\". Error %d.\n",
  344. obj_name_pt, res);
  345. }
  346. parent_object_handle = obj_handle;
  347. obj_name_pt = strtok_r (NULL, SEPERATOR_STR, &save_pt);
  348. }
  349. }
  350. static void tail_key_changed(confdb_handle_t handle,
  351. confdb_change_type_t change_type,
  352. hdb_handle_t parent_object_handle,
  353. hdb_handle_t object_handle,
  354. void *object_name_pt,
  355. int object_name_len,
  356. void *key_name_pt,
  357. int key_name_len,
  358. void *key_value_pt,
  359. int key_value_len)
  360. {
  361. char * on = (char*)object_name_pt;
  362. char * kn = (char*)key_name_pt;
  363. char * kv = (char*)key_value_pt;
  364. on[object_name_len] = '\0';
  365. kv[key_value_len] = '\0';
  366. kn[key_name_len] = '\0';
  367. printf("key_changed> %s.%s=%s\n", on, kn, kv);
  368. }
  369. static void tail_object_created(confdb_handle_t handle,
  370. hdb_handle_t parent_object_handle,
  371. hdb_handle_t object_handle,
  372. uint8_t *name_pt,
  373. int name_len)
  374. {
  375. name_pt[name_len] = '\0';
  376. printf("object_created> %s\n", name_pt);
  377. }
  378. static void tail_object_deleted(confdb_handle_t handle,
  379. hdb_handle_t parent_object_handle,
  380. uint8_t *name_pt,
  381. int name_len)
  382. {
  383. name_pt[name_len] = '\0';
  384. printf("object_deleted> %s\n", name_pt);
  385. }
  386. static void listen_for_object_changes(confdb_handle_t handle)
  387. {
  388. int result;
  389. fd_set read_fds;
  390. int select_fd;
  391. int quit = CS_FALSE;
  392. FD_ZERO (&read_fds);
  393. if (confdb_fd_get (handle, &select_fd) != CS_OK) {
  394. printf ("can't get the confdb selector object.\n");
  395. return;
  396. }
  397. printf ("Type \"q\" to finish\n");
  398. do {
  399. FD_SET (select_fd, &read_fds);
  400. FD_SET (STDIN_FILENO, &read_fds);
  401. result = select (select_fd + 1, &read_fds, 0, 0, 0);
  402. if (result == -1) {
  403. perror ("select\n");
  404. }
  405. if (FD_ISSET (STDIN_FILENO, &read_fds)) {
  406. char inbuf[3];
  407. if (fgets(inbuf, sizeof(inbuf), stdin) == NULL)
  408. quit = CS_TRUE;
  409. else if (strncmp(inbuf, "q", 1) == 0)
  410. quit = CS_TRUE;
  411. }
  412. if (FD_ISSET (select_fd, &read_fds)) {
  413. if (confdb_dispatch (handle, CONFDB_DISPATCH_ALL) != CS_OK)
  414. exit(1);
  415. }
  416. } while (result && quit == CS_FALSE);
  417. (void)confdb_stop_track_changes(handle);
  418. }
  419. static void track_object(confdb_handle_t handle, char * name_pt)
  420. {
  421. cs_error_t res;
  422. hdb_handle_t obj_handle;
  423. res = find_object (handle, name_pt, FIND_OBJECT_ONLY, &obj_handle);
  424. if (res != CS_OK) {
  425. fprintf (stderr, "Could not find object \"%s\". Error %d\n",
  426. name_pt, res);
  427. return;
  428. }
  429. res = confdb_track_changes (handle, obj_handle, CONFDB_TRACK_DEPTH_RECURSIVE);
  430. if (res != CS_OK) {
  431. fprintf (stderr, "Could not enable tracking on object \"%s\". Error %d\n",
  432. name_pt, res);
  433. return;
  434. }
  435. }
  436. static void stop_tracking(confdb_handle_t handle)
  437. {
  438. cs_error_t res;
  439. res = confdb_stop_track_changes (handle);
  440. if (res != CS_OK) {
  441. fprintf (stderr, "Could not stop tracking. Error %d\n", res);
  442. return;
  443. }
  444. }
  445. static void delete_object(confdb_handle_t handle, char * name_pt)
  446. {
  447. cs_error_t res;
  448. hdb_handle_t obj_handle;
  449. res = find_object (handle, name_pt, FIND_OBJECT_ONLY, &obj_handle);
  450. if (res == CS_OK) {
  451. res = confdb_object_destroy (handle, obj_handle);
  452. if (res != CS_OK)
  453. fprintf(stderr, "Failed to find object \"%s\" to delete. Error %d\n", name_pt, res);
  454. } else {
  455. char parent_name[OBJ_NAME_SIZE];
  456. char key_name[OBJ_NAME_SIZE];
  457. char key_value[OBJ_NAME_SIZE];
  458. /* find the parent object */
  459. get_parent_name(name_pt, parent_name);
  460. get_key(name_pt, key_name, key_value);
  461. res = find_object (handle, parent_name, FIND_OBJECT_ONLY, &obj_handle);
  462. if (res != CS_OK) {
  463. fprintf(stderr, "Failed to find the key's parent object \"%s\". Error %d\n", parent_name, res);
  464. exit (EXIT_FAILURE);
  465. }
  466. res = confdb_key_delete (handle,
  467. obj_handle,
  468. key_name,
  469. strlen(key_name),
  470. key_value,
  471. strlen(key_value));
  472. if (res != CS_OK)
  473. fprintf(stderr, "Failed to delete key \"%s=%s\" from object \"%s\". Error %d\n",
  474. key_name, key_value, parent_name, res);
  475. }
  476. }
  477. int main (int argc, char *argv[]) {
  478. confdb_handle_t handle;
  479. cs_error_t result;
  480. int c;
  481. action = ACTION_READ;
  482. for (;;){
  483. c = getopt (argc,argv,"hawcdtp:");
  484. if (c==-1) {
  485. break;
  486. }
  487. switch (c) {
  488. case 'h':
  489. return print_help();
  490. break;
  491. case 'a':
  492. action = ACTION_PRINT_ALL;
  493. break;
  494. case 'p':
  495. printf("%s:%d NOT Implemented yet.\n", __FUNCTION__, __LINE__);
  496. return -1;
  497. //return read_in_config_file();
  498. break;
  499. case 'c':
  500. action = ACTION_CREATE;
  501. break;
  502. case 'd':
  503. action = ACTION_DELETE;
  504. break;
  505. case 'w':
  506. action = ACTION_WRITE;
  507. break;
  508. case 't':
  509. action = ACTION_TRACK;
  510. break;
  511. default :
  512. action = ACTION_READ;
  513. break;
  514. }
  515. }
  516. if (argc == 1) {
  517. action = ACTION_PRINT_DEFAULT;
  518. return print_all();
  519. } else if (action == ACTION_PRINT_ALL) {
  520. return print_all();
  521. } else if (optind >= argc) {
  522. fprintf(stderr, "Expected an object path after options\n");
  523. exit(EXIT_FAILURE);
  524. }
  525. result = confdb_initialize (&handle, &callbacks);
  526. if (result != CS_OK) {
  527. fprintf (stderr, "Failed to initialize the objdb API. Error %d\n", result);
  528. exit (EXIT_FAILURE);
  529. }
  530. while (optind < argc) {
  531. switch (action) {
  532. case ACTION_READ:
  533. read_object(handle, argv[optind++]);
  534. break;
  535. case ACTION_WRITE:
  536. write_key(handle, argv[optind++]);
  537. break;
  538. case ACTION_CREATE:
  539. create_object(handle, argv[optind++]);
  540. break;
  541. case ACTION_DELETE:
  542. delete_object(handle, argv[optind++]);
  543. break;
  544. case ACTION_TRACK:
  545. track_object(handle, argv[optind++]);
  546. break;
  547. }
  548. }
  549. if (action == ACTION_TRACK) {
  550. listen_for_object_changes(handle);
  551. stop_tracking(handle);
  552. }
  553. result = confdb_finalize (handle);
  554. if (result != CS_OK) {
  555. fprintf (stderr, "Error finalizing objdb API. Error %d\n", result);
  556. exit(EXIT_FAILURE);
  557. }
  558. return 0;
  559. }