1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2008-2009, Intel Corporation.
  23  * All Rights Reserved.
  24  */
  25 
  26 #include <stdlib.h>
  27 #include <string.h>
  28 #include <memory.h>
  29 #include <stdio.h>
  30 #include <ctype.h>
  31 
  32 #include "latencytop.h"
  33 
  34 /*
  35  * Structure that holds detail of a cause.
  36  */
  37 typedef struct {
  38         int lt_c_cause_id;
  39         int lt_c_flags;
  40         char *lt_c_name;
  41 } lt_cause_t;
  42 
  43 /*
  44  * Structure that represents a matched cause.
  45  */
  46 typedef struct  {
  47         int lt_mt_priority;
  48         int lt_mt_cause_id;
  49 } lt_match_t;
  50 
  51 /* All lt_cause_t that are created. */
  52 static GHashTable *cause_lookup = NULL;
  53 static GPtrArray *causes_array = NULL;
  54 static int causes_array_len = 0;
  55 
  56 /*
  57  * This hash table maps a symbol to a cause.
  58  * key is of type "char *" and value is of type "lt_match_t *".
  59  */
  60 static GHashTable *symbol_lookup_table = NULL;
  61 
  62 /*
  63  * The dtrace translation rules we get from the script
  64  */
  65 char *dtrans = NULL;
  66 
  67 /*
  68  * These structures are only used inside .trans parser.
  69  */
  70 typedef struct {
  71         int lt_dm_priority;
  72         char *lt_dm_macro;
  73 } lt_dmacro_t;
  74 
  75 typedef struct {
  76         GSequence *lt_pr_cmd_disable;
  77         GHashTable *lt_pr_dmacro;
  78 } lt_parser_t;
  79 
  80 /* ARGSUSED */
  81 static void
  82 free_cause(lt_cause_t *cause, void *user)
  83 {
  84         g_assert(cause != NULL && cause->lt_c_name != NULL);
  85 
  86         free(cause->lt_c_name);
  87         free(cause);
  88 }
  89 
  90 static void
  91 free_dmacro(lt_dmacro_t *d)
  92 {
  93         g_assert(d->lt_dm_macro != NULL);
  94         free(d->lt_dm_macro);
  95         free(d);
  96 }
  97 
  98 /*
  99  * Add a cause.
 100  */
 101 static lt_cause_t *
 102 new_cause(char *name, int flags)
 103 {
 104         lt_cause_t *entry;
 105 
 106         g_assert(name != NULL);
 107 
 108         entry = (lt_cause_t *)lt_malloc(sizeof (lt_cause_t));
 109         entry->lt_c_flags = flags;
 110         entry->lt_c_name = name;
 111         entry->lt_c_cause_id = causes_array_len;
 112 
 113         g_ptr_array_add(causes_array, entry);
 114         ++causes_array_len;
 115 
 116         return (entry);
 117 }
 118 
 119 /*
 120  * Set a cause to "disabled" state.
 121  */
 122 static void
 123 disable_cause(char *cause_str, GHashTable *cause_table)
 124 {
 125         lt_cause_t *cause;
 126 
 127         cause = (lt_cause_t *)g_hash_table_lookup(cause_table, cause_str);
 128 
 129         if (cause != NULL) {
 130                 cause->lt_c_flags |= CAUSE_FLAG_DISABLED;
 131         }
 132 }
 133 
 134 /*
 135  * Helper functions that reads a line from a character array.
 136  */
 137 static int
 138 read_line_from_mem(const char *mem, int mem_len, char *line, int line_len,
 139     int *index)
 140 {
 141         g_assert(mem != NULL && line != NULL && index != NULL);
 142 
 143         if (line_len <= 0 || mem_len <= 0) {
 144                 return (0);
 145         }
 146 
 147         if (*index >= mem_len) {
 148                 return (0);
 149         }
 150 
 151         while (line_len > 1 && *index < mem_len) {
 152                 *line = mem[(*index)++];
 153                 --line_len;
 154                 ++line;
 155 
 156                 if (*(line-1) == '\r' || *(line-1) == '\n') {
 157                         break;
 158                 }
 159         }
 160         *line = '\0';
 161 
 162         return (1);
 163 }
 164 
 165 /*
 166  * Parse special command from configuration file. Special command
 167  * has the following format :
 168 
 169  *      disable_cause <cause name>
 170  */
 171 static int
 172 parse_config_cmd(char *begin, lt_parser_t *parser)
 173 {
 174         char *tmp;
 175         char old_chr = 0;
 176 
 177         /*
 178          * disable_cause  FSFlush Daemon
 179          * ^
 180          */
 181         if (*begin == '\0') {
 182                 return (0);
 183         }
 184 
 185         for (tmp = begin;
 186             *tmp != '\0' && !isspace(*tmp);
 187             ++tmp) {
 188         }
 189         old_chr = *tmp;
 190         *tmp = '\0';
 191 
 192         if (strcmp("disable_cause", begin) == 0) {
 193                 if (old_chr == '\0') {
 194                         /* Must have an argument */
 195                         lt_display_error(
 196                             "Invalid command format: %s\n",
 197                             begin);
 198                         return (-1);
 199                 }
 200 
 201                 begin = tmp+1;
 202                 while (isspace(*begin)) {
 203                         ++begin;
 204                 }
 205 
 206                 g_sequence_append(parser->lt_pr_cmd_disable,
 207                     lt_strdup(begin));
 208         } else   {
 209                 *tmp = old_chr;
 210                 lt_display_error(
 211                     "Unknown command: %s\n", begin);
 212                 return (-1);
 213         }
 214 
 215         return (0);
 216 }
 217 
 218 /*
 219  * Parse symbol translation from configuration file. Symbol translation
 220  * has the following format :
 221  *
 222  *      <priority> <symbol name> <cause>
 223  *
 224  * Finally check if that cause has already been mapped.
 225  */
 226 static int
 227 parse_sym_trans(char *begin)
 228 {
 229         int priority = 0;
 230         char *match;
 231         char *match_dup;
 232         char *cause_str;
 233         lt_cause_t *cause;
 234         lt_match_t *match_entry;
 235         char *tmp;
 236 
 237         /*
 238          * 10   genunix`pread                   Syscall pread
 239          * ^
 240          */
 241         priority = strtol(begin, &tmp, 10);
 242 
 243         if (tmp == begin || priority == 0) {
 244                 return (-1);
 245         }
 246 
 247         begin = tmp;
 248 
 249         /*
 250          * 10   genunix`pread                   Syscall pread
 251          * --^
 252          */
 253 
 254         if (!isspace(*begin)) {
 255                 /* At least one space char after <priority> */
 256                 return (-1);
 257         }
 258 
 259         while (isspace(*begin)) {
 260                 ++begin;
 261         }
 262 
 263         if (*begin == 0) {
 264                 return (-1);
 265         }
 266 
 267         /*
 268          * 10   genunix`pread                   Syscall pread
 269          * -----^
 270          */
 271         for (tmp = begin;
 272             *tmp != '\0' && !isspace(*tmp);
 273             ++tmp) {
 274         }
 275 
 276         if (*tmp == '\0') {
 277                 return (-1);
 278         }
 279 
 280         *tmp = '\0';
 281         match = begin;
 282 
 283         /* Check if we have mapped this function before. */
 284         match_entry = (lt_match_t *)
 285             g_hash_table_lookup(symbol_lookup_table, match);
 286 
 287         if (match_entry != NULL &&
 288             HIGHER_PRIORITY(match_entry->lt_mt_priority, priority)) {
 289                 /* We already have a higher entry. Ignore this. */
 290                 return (0);
 291         }
 292 
 293         begin = tmp + 1;
 294 
 295         /*
 296          * 10   genunix`pread                   Syscall pread
 297          * -------------------------------------^
 298          */
 299         while (isspace(*begin)) {
 300                 ++begin;
 301         }
 302 
 303         if (*begin == 0) {
 304                 return (-1);
 305         }
 306 
 307         cause_str = begin;
 308 
 309         /* Check if we have mapped this cause before. */
 310         cause = (lt_cause_t *)
 311             g_hash_table_lookup(cause_lookup, cause_str);
 312 
 313         if (cause == NULL) {
 314                 char *cause_dup = lt_strdup(cause_str);
 315                 cause = new_cause(cause_dup, 0);
 316                 g_hash_table_insert(cause_lookup, cause_dup, cause);
 317         }
 318 
 319         match_entry = (lt_match_t *)lt_malloc(sizeof (lt_match_t));
 320         match_entry->lt_mt_priority = priority;
 321         match_entry->lt_mt_cause_id = cause->lt_c_cause_id;
 322         match_dup = lt_strdup(match);
 323 
 324         g_hash_table_insert(symbol_lookup_table, match_dup,
 325             match_entry);
 326 
 327         return (0);
 328 }
 329 
 330 /*
 331  * Parse D macro. D macros have the following format :
 332  *
 333  *      <priority> <entry probe> <return probe> <cause>
 334  *
 335  * Finally check if that cause has already been mapped.
 336  */
 337 static int
 338 parse_dmacro(char *begin, lt_parser_t *parser)
 339 {
 340         int priority = 0;
 341         char *entryprobe;
 342         char *returnprobe;
 343         char *cause_str;
 344         char buf[512];
 345         char probepair[512];
 346         char *tmp = NULL;
 347         lt_cause_t *cause;
 348         lt_dmacro_t *dmacro;
 349 
 350         /*
 351          * 10   syscall::pread:entry    syscall::pread:return   Syscall pread
 352          * ^
 353          */
 354         priority = strtol(begin, &tmp, 10);
 355 
 356         if (tmp == begin || priority == 0) {
 357                 return (-1);
 358         }
 359 
 360         begin = tmp;
 361 
 362         /*
 363          * 10   syscall::pread:entry    syscall::pread:return   Syscall pread
 364          * --^
 365          */
 366         while (isspace(*begin)) {
 367                 ++begin;
 368         }
 369 
 370         if (*begin == 0) {
 371                 return (-1);
 372         }
 373 
 374         /*
 375          * 10   syscall::pread:entry    syscall::pread:return   Syscall pread
 376          * -----^
 377          */
 378         for (tmp = begin;
 379             *tmp != '\0' && !isspace(*tmp);
 380             ++tmp) {
 381         }
 382 
 383         if (*tmp == '\0') {
 384                 return (-1);
 385         }
 386 
 387         *tmp = '\0';
 388         entryprobe = begin;
 389         begin = tmp + 1;
 390 
 391         while (isspace(*begin)) {
 392                 ++begin;
 393         }
 394 
 395         /*
 396          * 10   syscall::pread:entry    syscall::pread:return   Syscall pread
 397          * -----------------------------^
 398          */
 399         for (tmp = begin;
 400             *tmp != '\0' && !isspace(*tmp);
 401             ++tmp) {
 402         }
 403 
 404         if (*tmp == '\0') {
 405                 return (-1);
 406         }
 407 
 408         *tmp = '\0';
 409         returnprobe = begin;
 410         begin = tmp + 1;
 411 
 412         while (isspace(*begin)) {
 413                 ++begin;
 414         }
 415 
 416         /*
 417          * 10   syscall::pread:entry    syscall::pread:return   Syscall pread
 418          * -----------------------------------------------------^
 419          */
 420         if (*begin == 0) {
 421                 return (-1);
 422         }
 423 
 424         cause_str = begin;
 425 
 426         dmacro = NULL;
 427 
 428         /* Check if we have mapped this cause before. */
 429         cause = (lt_cause_t *)
 430             g_hash_table_lookup(cause_lookup, cause_str);
 431 
 432         if (cause == NULL) {
 433                 char *cause_dup = lt_strdup(cause_str);
 434                 cause = new_cause(cause_dup, 0);
 435                 g_hash_table_insert(cause_lookup, cause_dup, cause);
 436         }
 437 
 438         (void) snprintf(buf, sizeof (buf), "\nTRANSLATE(%s, %s, \"%s\", %d)\n",
 439             entryprobe, returnprobe, cause_str, priority);
 440 
 441         (void) snprintf(probepair, sizeof (probepair), "%s %s", entryprobe,
 442             returnprobe);
 443 
 444         g_assert(cause != NULL);
 445         g_assert(parser->lt_pr_dmacro != NULL);
 446 
 447         dmacro = g_hash_table_lookup(parser->lt_pr_dmacro, probepair);
 448 
 449         if (dmacro == NULL) {
 450                 dmacro = (lt_dmacro_t *)lt_malloc(sizeof (lt_dmacro_t));
 451                 dmacro->lt_dm_priority = priority;
 452                 dmacro->lt_dm_macro = lt_strdup(buf);
 453                 g_hash_table_insert(parser->lt_pr_dmacro, lt_strdup(probepair),
 454                     dmacro);
 455         } else if (dmacro->lt_dm_priority < priority) {
 456                 free(dmacro->lt_dm_macro);
 457                 dmacro->lt_dm_priority = priority;
 458                 dmacro->lt_dm_macro = lt_strdup(buf);
 459         }
 460 
 461         return (0);
 462 }
 463 
 464 /*
 465  * Helper function to collect TRANSLATE() macros.
 466  */
 467 /* ARGSUSED */
 468 static void
 469 genscript(void *key, lt_dmacro_t *dmacro, GString *str)
 470 {
 471         g_string_append(str, dmacro->lt_dm_macro);
 472 }
 473 
 474 /*
 475  * Main logic that parses translation rules one line at a time,
 476  * and creates a lookup table from it. The syntax for the translation
 477  * is as follows :
 478  *
 479  *      #                               <--- comment
 480  *      D <D macro rule>          <--- D macro
 481  *      S <Symbol translation>            <--- Symbols
 482  *      disable_cause <cause>             <--- special command
 483  */
 484 static int
 485 parse_config(const char *work, int work_len)
 486 {
 487         char line[256];
 488         int len;
 489         char *begin, *end;
 490         int current = 0;
 491         lt_parser_t parser;
 492         int ret = 0;
 493         char flag;
 494         GString *script;
 495 
 496         cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
 497         lt_check_null(cause_lookup);
 498 
 499         parser.lt_pr_cmd_disable = g_sequence_new((GDestroyNotify)free);
 500         lt_check_null(parser.lt_pr_cmd_disable);
 501 
 502         parser.lt_pr_dmacro = g_hash_table_new_full(g_str_hash,
 503             g_str_equal, (GDestroyNotify)free, (GDestroyNotify)free_dmacro);
 504         lt_check_null(parser.lt_pr_dmacro);
 505 
 506         while (read_line_from_mem(work, work_len, line, sizeof (line),
 507             &current)) {
 508                 len = strlen(line);
 509 
 510                 if (line[len-1] != '\n' && line[len-1] != '\r' &&
 511                     current < work_len) {
 512                         lt_display_error("Configuration line too long.\n");
 513                         goto err;
 514                 }
 515 
 516                 begin = line;
 517 
 518                 while (isspace(*begin)) {
 519                         ++begin;
 520                 }
 521 
 522                 if (*begin == '\0') {
 523                         /* Ignore empty line */
 524                         continue;
 525                 }
 526 
 527                 /* Delete trailing spaces. */
 528                 end = begin + strlen(begin) - 1;
 529 
 530                 while (isspace(*end)) {
 531                         --end;
 532                 }
 533 
 534                 end[1] = '\0';
 535 
 536                 flag = *begin;
 537                 ++begin;
 538 
 539                 switch (flag) {
 540                 case '#':
 541                         ret = 0;
 542                         break;
 543                 case ';':
 544                         ret = parse_config_cmd(begin, &parser);
 545                         break;
 546                 case 'D':
 547                 case 'd':
 548                         if (!isspace(*begin)) {
 549                                 lt_display_error(
 550                                     "No space after flag char: %s\n", line);
 551                         }
 552                         while (isspace(*begin)) {
 553                                 ++begin;
 554                         }
 555                         ret = parse_dmacro(begin, &parser);
 556                         break;
 557                 case 'S':
 558                 case 's':
 559                         if (!isspace(*begin)) {
 560                                 lt_display_error(
 561                                     "No space after flag char: %s\n", line);
 562                         }
 563                         while (isspace(*begin)) {
 564                                 ++begin;
 565                         }
 566                         ret = parse_sym_trans(begin);
 567                         break;
 568                 default:
 569                         ret = -1;
 570                         break;
 571                 }
 572 
 573                 if (ret != 0) {
 574                         lt_display_error(
 575                             "Invalid configuration line: %s\n", line);
 576                         goto err;
 577                 }
 578         }
 579 
 580         script = g_string_new(NULL);
 581         g_hash_table_foreach(parser.lt_pr_dmacro, (GHFunc)genscript, script);
 582         dtrans = g_string_free(script, FALSE);
 583 
 584         if (dtrans != NULL && strlen(dtrans) == 0) {
 585                 free(dtrans);
 586                 dtrans = NULL;
 587         }
 588 
 589         g_sequence_foreach(parser.lt_pr_cmd_disable, (GFunc)disable_cause,
 590             cause_lookup);
 591         g_sequence_free(parser.lt_pr_cmd_disable);
 592 
 593         return (0);
 594 
 595 err:
 596         g_sequence_free(parser.lt_pr_cmd_disable);
 597         g_hash_table_destroy(parser.lt_pr_dmacro);
 598         return (-1);
 599 
 600 }
 601 
 602 /*
 603  * Init function, called when latencytop starts.
 604  * It loads translation rules from the configuration file. The configuration
 605  * file defines some causes and symbols that match those causes.
 606  */
 607 int
 608 lt_table_init(void)
 609 {
 610         char *config_loaded = NULL;
 611         int config_loaded_len = 0;
 612         const char *work = NULL;
 613         int work_len = 0;
 614         lt_cause_t *cause;
 615 
 616 #ifdef EMBED_CONFIGS
 617         work = &latencytop_trans_start;
 618         work_len = (int)(&latencytop_trans_end - &latencytop_trans_start);
 619 #endif
 620 
 621         if (g_config.lt_cfg_config_name != NULL) {
 622                 FILE *fp;
 623                 fp = fopen(g_config.lt_cfg_config_name, "r");
 624 
 625                 if (NULL == fp) {
 626                         lt_display_error(
 627                             "Unable to open configuration file.\n");
 628                         return (-1);
 629                 }
 630 
 631                 (void) fseek(fp, 0, SEEK_END);
 632                 config_loaded_len = (int)ftell(fp);
 633                 config_loaded = (char *)lt_malloc(config_loaded_len);
 634                 (void) fseek(fp, 0, SEEK_SET);
 635 
 636                 /* A zero-byte translation is valid */
 637                 if (config_loaded_len != 0 &&
 638                     fread(config_loaded, config_loaded_len, 1, fp) == 0) {
 639                         lt_display_error(
 640                             "Unable to read configuration file.\n");
 641                         (void) fclose(fp);
 642                         free(config_loaded);
 643                         return (-1);
 644                 }
 645 
 646                 (void) fclose(fp);
 647                 (void) printf("Loaded configuration from %s\n",
 648                     g_config.lt_cfg_config_name);
 649 
 650                 work = config_loaded;
 651                 work_len = config_loaded_len;
 652         }
 653 
 654         lt_table_deinit();
 655         causes_array = g_ptr_array_new();
 656         lt_check_null(causes_array);
 657 
 658         /* 0 is not used, but it is kept as a place for bugs etc. */
 659         cause = new_cause(lt_strdup("Nothing"), CAUSE_FLAG_DISABLED);
 660         g_assert(cause->lt_c_cause_id == INVALID_CAUSE);
 661 
 662         symbol_lookup_table = g_hash_table_new_full(
 663             g_str_hash, g_str_equal,
 664             (GDestroyNotify)free, (GDestroyNotify)free);
 665         lt_check_null(symbol_lookup_table);
 666 
 667         if (work_len != 0 && parse_config(work, work_len) != 0) {
 668                 return (-1);
 669         }
 670 
 671         if (config_loaded != NULL) {
 672                 free(config_loaded);
 673         }
 674 
 675         return (0);
 676 }
 677 
 678 /*
 679  * Some causes, such as "lock spinning", do not have stack trace. Names
 680  * of such causes are explicitly specified in the D script.
 681  * This function resolves such causes and dynamically adds them
 682  * to the global tables when they are found first. If auto_create is set
 683  * to TRUE, the entry will be created if it is not found.
 684  * Return cause_id of the cause.
 685  */
 686 int
 687 lt_table_cause_from_name(char *name, int auto_create, int flags)
 688 {
 689         lt_cause_t *cause = NULL;
 690 
 691         if (cause_lookup == NULL) {
 692                 cause_lookup = g_hash_table_new(g_str_hash, g_str_equal);
 693                 lt_check_null(cause_lookup);
 694         } else   {
 695                 cause = (lt_cause_t *)
 696                     g_hash_table_lookup(cause_lookup, name);
 697         }
 698 
 699         if (cause == NULL && auto_create) {
 700                 char *cause_dup;
 701 
 702                 if (name[0] == '#') {
 703                         flags |= CAUSE_FLAG_HIDE_IN_SUMMARY;
 704                 }
 705 
 706                 cause_dup = lt_strdup(name);
 707                 cause = new_cause(cause_dup, flags);
 708                 g_hash_table_insert(cause_lookup, cause_dup, cause);
 709         }
 710 
 711         return (cause == NULL ? INVALID_CAUSE : cause->lt_c_cause_id);
 712 }
 713 
 714 /*
 715  * Try to map a symbol on stack to a known cause.
 716  * module_func has the format "module_name`function_name".
 717  * cause_id and priority will be set if a cause is found.
 718  * If cause is found return 1, otherwise return 0.
 719  */
 720 int
 721 lt_table_cause_from_stack(const char *module_func, int *cause_id, int *priority)
 722 {
 723         lt_match_t *match;
 724 
 725         g_assert(module_func != NULL && cause_id != NULL && priority != NULL);
 726 
 727         if (symbol_lookup_table == NULL) {
 728                 return (0);
 729         }
 730 
 731         match = (lt_match_t *)
 732             g_hash_table_lookup(symbol_lookup_table, module_func);
 733 
 734         if (match == NULL) {
 735                 char *func = strchr(module_func, '`');
 736 
 737                 if (func != NULL) {
 738                         match = (lt_match_t *)
 739                             g_hash_table_lookup(symbol_lookup_table, func);
 740                 }
 741         }
 742 
 743         if (match == NULL) {
 744                 return (0);
 745         } else   {
 746                 *cause_id = match->lt_mt_cause_id;
 747                 *priority = match->lt_mt_priority;
 748                 return (1);
 749         }
 750 }
 751 
 752 /*
 753  * Get the display name of a cause. cause_id must be valid,
 754  * it is usually returned from lt_table_cause_from_stack() or
 755  * lt_table_cause_from_name().
 756  */
 757 const char *
 758 lt_table_get_cause_name(int cause_id)
 759 {
 760         lt_cause_t *cause;
 761 
 762         if (cause_id < 0 || cause_id >= causes_array_len) {
 763                 return (NULL);
 764         }
 765 
 766         cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
 767 
 768         if (cause == NULL) {
 769                 return (NULL);
 770         } else {
 771                 return (cause->lt_c_name);
 772         }
 773 }
 774 
 775 /*
 776  * Check cause flag.
 777  * If CAUSE_ALL_FLAGS is passed in, all flags are returned.
 778  */
 779 int
 780 lt_table_get_cause_flag(int cause_id, int flag)
 781 {
 782         lt_cause_t *cause;
 783 
 784         if (cause_id < 0 || cause_id >= causes_array_len) {
 785                 return (0);
 786         }
 787 
 788         cause = (lt_cause_t *)g_ptr_array_index(causes_array, cause_id);
 789 
 790         if (cause == NULL) {
 791                 return (0);
 792         } else {
 793                 return (cause->lt_c_flags & flag);
 794         }
 795 }
 796 
 797 /*
 798  * Append macros to D script, if any.
 799  */
 800 int
 801 lt_table_append_trans(FILE *fp)
 802 {
 803         if (dtrans != NULL) {
 804                 if (fwrite(dtrans, strlen(dtrans), 1, fp) != 1) {
 805                         return (-1);
 806                 }
 807         }
 808 
 809         return (0);
 810 }
 811 
 812 /*
 813  * Clean up function.
 814  * Free the resources used for symbol table (symbols, causes etc.).
 815  */
 816 void
 817 lt_table_deinit(void)
 818 {
 819         if (symbol_lookup_table != NULL) {
 820                 g_hash_table_destroy(symbol_lookup_table);
 821                 symbol_lookup_table = NULL;
 822         }
 823 
 824         if (cause_lookup != NULL) {
 825                 g_hash_table_destroy(cause_lookup);
 826                 cause_lookup = NULL;
 827         }
 828 
 829         if (causes_array != NULL) {
 830                 g_ptr_array_foreach(causes_array, (GFunc)free_cause, NULL);
 831                 g_ptr_array_free(causes_array, TRUE);
 832                 causes_array = NULL;
 833                 causes_array_len = 0;
 834         }
 835 
 836         if (dtrans != NULL) {
 837                 g_free(dtrans);
 838                 dtrans = NULL;
 839         }
 840 }