xref: /titanic_52/usr/src/cmd/latencytop/common/table.c (revision fca4268092e9961ebb9b5e0098dcebc545023586)
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 }
841