static char rcsid[] = "@(#)$Id: debug.c,v 1.23 2006/04/09 07:37:08 hurtta Exp $"; /****************************************************************************** * The Elm (ME+) Mail System - $Revision: 1.23 $ $State: Exp $ * * Author: Kari Hurtta (was hurtta+elm@ozone.FMI.FI) *****************************************************************************/ #include "headers.h" #include "s_elm.h" #include "patchlevel.h" static char *us2s P_((unsigned char *str)); static char *us2s(str) unsigned char *str; { return (char *)str; } struct debug_target { int debug_fd; CONST char * file; struct class_level_pair { CONST char * class; int level; } * match; int match_count; struct debug_target * next; /* In startup quite huge number of debug output can be needed to be buffered */ char buffer[4000]; VOLATILE int buffer_ptr; } * debug_list = NULL; static CONST char * DEBUG_base = "ELMLIB"; /* Given arhument must be statically alloced */ void init_debugfile(progname) CONST char *progname; { DEBUG_base = progname; } static int flush_debug_buffer P_((struct debug_target *a)); static void init_debug P_((struct debug_target * A)); static void init_debug(A) struct debug_target * A; { DEBUG_VAR(temp,__FILE__,""); char * filename = elm_message(FRM("%s/%s"),home,A->file); char * pp; int fd = -1; temp.target = A; /* For compability of old method "ELM:debug.info" is renamed to "ELM:debug.last" */ if (NULL != (pp = strrchr(A->file,':')) && 0 == strcmp(pp,":debug.info") && access(filename, ACCESS_EXISTS) == 0) { /* already one! */ char *newfname = elm_message(FRM("%s/%s:debug.last"), home,DEBUG_base); if (0 == rename(filename, newfname)) { debug_action_call(&temp, "RENAMEd old debug file %s to %s\n", filename,newfname); } free(newfname); } if (0 == can_open(filename,"a")) fd = open(filename,O_CREAT|O_WRONLY|O_APPEND,00600); if (-1 == fd) { lib_error(CATGETS(elm_msg_cat, ElmSet, ElmCouldNotOpenDebugFile, "Could not open file %s for debug output!\n"), filename); goto out; } if (-1 != A->debug_fd) close(A->debug_fd); A->debug_fd = fd; elm_chown(filename, userid, groupid); /* file owned by user */ if (0 != A->buffer_ptr) debug_action_call(&temp, "\n======================================================\n"); debug_action_call(&temp, "Debug output of the ELM library. Version %s PL%s\n\n", VERSION,PATCHLEVEL); out: free(filename); } static void close_all(void); static void close_all() { DEBUG_VAR(temp,__FILE__,""); struct debug_target * A; for (A = debug_list; A; A = A->next) { temp.target = A; debug_action_call(&temp,"\nClosing debug file...\n"); flush_debug_buffer(A); if (-1 != A->debug_fd) close(A->debug_fd); A->debug_fd = -1; } } int set_debugging(arg) CONST char *arg; { struct debug_target * X = NULL; char * match_filename = NULL; struct class_level_pair C; struct class_level_pair *tmp1; DEBUG_VAR(temp,__FILE__,""); char * ptr; long l; C.class = ""; C.level = 0; l = strtol(arg,&ptr,10); if ('\0' == *ptr) C.level = l; else if (NULL == (ptr = strpbrk(arg,":="))) { C.class = arg; C.level = 1; } else { char *ptr1; char * tmp__1 = safe_strdup(arg); tmp__1[ptr-arg] = '\0'; C.class = tmp__1; if ('=' == *ptr) { match_filename = safe_strdup(ptr+1); ptr = strchr(match_filename,':'); if (ptr) { *ptr = '\0'; ptr++; } } else ptr++; if (!ptr) C.level = 1; else { l = strtol(ptr,&ptr1,10); if ('\0' != *ptr1) { if (match_filename) free(match_filename); return 0; } C.level = l; } } if (!match_filename) match_filename = elm_message(FRM("%s:debug.info"), DEBUG_base); for (X = debug_list; X; X = X -> next) if (0 == strcmp(X->file,match_filename)) break; if (X) { free(match_filename); match_filename = NULL; } else { X = safe_malloc(sizeof (*X)); X->debug_fd = -1; X->file = match_filename; X->next = debug_list; X->match = NULL; X->match_count = 0; X->buffer[0] = '\0'; X->buffer_ptr = 0; match_filename = NULL; if (home[0]) init_debug(X); debug_list = X; } /* safe_realloc() calls DPRINT and therefore may refere X->match before it is assigned .. so we do not use safe_realloc() on here ... */ tmp1 = realloc(X->match, (1 + X->match_count) * sizeof (X->match[0])); if (tmp1) { X->match = tmp1; X->match[X->match_count++] = C; } else { debug_action_call(&temp, "Can't alloc memory for class %s at debug level %d (size %d)\n", C.class,C.level, (1 + X->match_count) * sizeof (X->match[0])); } if (!debug_list) atexit(close_all); temp.target = X; debug_action_call(&temp, "Debug output for class %s at debug level %d\n", C.class,C.level); return 1; } void debug_user_init() { struct debug_target * A; /* frm calls user_init() several times ... */ static int init_done = 0; if (init_done) return; init_done = 1; for (A = debug_list; A; A = A->next) init_debug(A); } void debug_level_check(a) struct debug_struct *a; { struct debug_target * A; for (A = debug_list; A; A = A->next) { int i; for (i = 0; i < A->match_count; i++) { if ('\0' == A->match[i].class[0] || 0 == strcmp(a->class,A->match[i].class)) { a->target = A; a->active = A->match[i].level; if (a->active > 0) { /* Not really called from signal handler, but better safe and anyway this for this debug_action_sigcall() is enough */ debug_action_sigcall(a,"\n"); debug_action_sigcall(a, "\n======================================================\n"); debug_action_sigcall(a,"Class: %.10s File: %s\n", a->class,a->file); debug_action_sigcall(a," : %s\n",a->ID); debug_action_sigcall(a, "======================================================\n"); } return; } } } /* If user initialized, mark that debug_level_check() need not recalled */ if ('\0' != home[0]) a->active = 0; } /* can be called from signal handler!! RACE condition: May lose characters */ static int flush_debug_buffer(a) struct debug_target *a; { if (a->debug_fd != -1) { int X = write(a->debug_fd,a->buffer,a->buffer_ptr); if (X > 0) { int len = a->buffer_ptr; len -= X; if (len > 0) memmove(a->buffer,a->buffer+X,len); if (len >= 0) a->buffer_ptr = len; else a->buffer_ptr = 0; return 1; } } return 0; } #define INC(p) { (p)++; if (!*(p)) break; } /* RACE condition: May lose some characters when called from signal handler */ static void PUTC P_((struct debug_target *a, int c)); #if __GNUC__ __inline__ #endif static void PUTC(a,c) struct debug_target *a; int c; { int x; if (a->buffer_ptr >= sizeof a->buffer) if (!flush_debug_buffer(a)) return; /* Buffer full -- can't flush */ x = a->buffer_ptr++; if (x < sizeof a->buffer) { a->buffer[x] = c; if ('\n' == c) flush_debug_buffer(a); } } static void print_prefix P_((struct debug_struct *a)); static void print_prefix(a) struct debug_struct *a; { /* Prefix with file name */ if (0 == a->target->buffer_ptr || a->target->buffer_ptr > 0 && a->target->buffer_ptr < sizeof (a->target->buffer) && '\n' == a->target->buffer[a->target->buffer_ptr-1]) { int i; for (i = 0; i < 10 && a->file[i]; i++) { PUTC(a->target,a->file[i]); } for (;i < 10; i++) { PUTC(a->target,' '); } PUTC(a->target,':'); PUTC(a->target,' '); } } #define SKIP_TO_ARG(a,s) \ if (*s != '%') { PUTC(a->target,*s); \ if ('\n' == *s && '\0' != *(s+1)) { print_prefix(a); } \ continue; } \ INC(s); \ if ('%' == *s) { PUTC(a->target,*s); continue; } #define SKIP_WIDTH(a,s) \ if ('0' == *s) { INC(s); } \ else if ('-' == *s) { INC(s); } \ else if ('+' == *s) { INC(s); } \ if (*s >= '1' && *s <= '9') { \ while (*s >= '0' && *s <= '9') { INC(s); } \ if ('$' == *s) { PUTC(a,'?'); break; } \ } else if ('*' == *s) { INC(s); } \ if ('.' == *s) { \ INC(s); \ if (*s >= '1' && *s <= '9') { \ while (*s >= '0' && *s <= '9') { INC(s); } \ } else if ('*' == *s) { INC(s); } \ } \ if ('l' == *s) { INC(s); } #define MAX_ARGS 5 #define MAX_WIDTH 128 /* This may called from signal handler, so do not malloc memory on here! */ static int handle_common P_((struct debug_target *target, struct format_elem *elems, int pos)); static int handle_common(target,elems,pos) struct debug_target *target; struct format_elem *elems; int pos; { char buffer[MAX_WIDTH]; int len; switch (elems[pos].format_chr) { int l; char *str, *a; int quote; case 'c': if (V_chr_val != elems[pos].type) { PUTC(target,'?'); PUTC(target,'t'); PUTC(target,'?'); return 0; } l = 1; while (l < elems[pos].val1 && !elems[pos].left) { PUTC(target,elems[pos].fill); l++; } PUTC(target,elems[pos].value.chr_val); while (l < elems[pos].val1 && elems[pos].left) { PUTC(target,' '); l++; } return 1; case 'p': case 'd': case'i': case 'x': case 'X': case 'o': case 'u': case 'f': len = convert_number(buffer, sizeof buffer, &(elems[pos])); if (0 == len) { PUTC(target,'?'); PUTC(target,'l'); PUTC(target,'?'); return 0; } for (l = 0; l < len; l++) { PUTC(target,buffer[l]); } return 1; case 's': case 'Q': if (V_str_val != elems[pos].type) { PUTC(target,'?'); return 0; } str = elems[pos].value.str_val; quote = ('Q' == elems[pos].format_chr); if (!str) { PUTC(target,'?'); PUTC(target,'t'); PUTC(target,'?'); return 0; } l = strlen(str); if (quote) { l += 2; for (a=str; *a; a++) { if (*a == '\\' || *a == '"') l++; } } if (elems[pos].val2 <= 0) elems[pos].val2 = l; if (quote) elems[pos].val2 -= 2; while (l < elems[pos].val1 && !elems[pos].left) { PUTC(target,elems[pos].fill); l++; } if (quote) { PUTC(target,'"'); } for (a = str; *a && a - str < elems[pos].val2; a++) { if (quote && (*a == '\\' || *a == '"')) { PUTC(target,'\\'); } PUTC(target,*a); } if (quote) { PUTC(target,'"'); } while (l < elems[pos].val1 && elems[pos].left) { PUTC(target,' '); l++; } return 1; } return 0; } /* Must NOT be called from signal handler ... */ void debug_action_call( #if ANSI_C struct debug_struct *a, const char * format, ... #else a, format, va_alist #endif ) #if !ANSI_C struct debug_struct *a; CONST char * format; va_dcl #endif { int count, x, max_elems; char * format_error = NULL; int pos; struct format_elem A[MAX_ARGS], *elems; CONST char * s; va_list vl; if (!a->target) return; print_prefix(a); Va_start(vl, format); /* defined in defs.h */ x = 0; for (s = format; *s; s++) if ('%' == *s && '%' != *(s+1)) x++; if (x > MAX_ARGS && /* Allow failure of allocation! */ NULL != (elems = malloc(x * sizeof (*elems)))) max_elems = x; else { elems = A; max_elems = MAX_ARGS; } count = parse_format_args (elems,max_elems, format,vl, &format_error); if (format_error) { PUTC(a->target,'?'); PUTC(a->target,'\n'); for (s = format_error; *s; s++) { PUTC(a->target,*s); } PUTC(a->target,'\n'); } pos = -1; for (s = format; *s; s++) { SKIP_TO_ARG(a,s); pos++; SKIP_WIDTH(a->target,s); if (pos < 0 || pos >= count) { PUTC(a->target,'?'); PUTC(a->target,'O'); PUTC(a->target,'?'); break; } if (elems[pos].format_chr != *s) { PUTC(a->target,'?'); PUTC(a->target,'f'); PUTC(a->target,'?'); break; } if (!handle_common(a->target,elems,pos)) switch(*s) { struct string * S, *S1; struct charset_state *ch; char *temp; int l,X; char *p; case 'C': if (V_cs_val != elems[pos].type) { PUTC(a->target,'?'); break; } ch = elems[pos].value.cs_val; S = new_string(display_charset); add_state_to_string(S,ch); temp = us2s(stream_from_string(S,1,NULL)); l = 1; while (l < elems[pos].val1 && !elems[pos].left) { PUTC(a->target,elems[pos].fill); l++; } for (p=temp; *p; p++) { PUTC(a->target,*p); } while (l < elems[pos].val1 && elems[pos].left) { PUTC(a->target,' '); l++; } free(temp); free_string(&S); break; case 'S': if (V_string_val != elems[pos].type) { PUTC(a->target,'?'); break; } S = elems[pos].value.string_val; if (!S) { PUTC(a->target,'?'); break; } if (!verify_string(S)) { PUTC(a->target,'?'); break; } S1 = convert_string(display_charset,S,1); if (!verify_string(S1)) { PUTC(a->target,'?'); break; } l = string_len(S1); if (elems[pos].val2 <= 0) elems[pos].val2 = l; X = 0; temp = us2s(streamclip_from_string(S1,&X,elems[pos].val2, NULL,NULL)); /* TODO: Should use printable_len ? */ while (l < elems[pos].val1 && !elems[pos].left) { PUTC(a->target,elems[pos].fill); l++; } for (p=temp; *p; p++) { PUTC(a->target,*p); } free(temp); free_string(&S1); while (l < elems[pos].val1 && elems[pos].left) { PUTC(a->target,' '); l++; } break; default: PUTC(a->target,'?'); PUTC(a->target,'u'); PUTC(a->target,'?'); break; } } if (elems != A) { free(elems); elems = NULL; } va_end(vl); } /* resulting FILE * can be fclosed after use */ FILE * debug_to_FILE(a) struct debug_struct *a; { if (!a->target) return NULL; flush_debug_buffer(a->target); if (a->target->debug_fd != -1) { int fd = dup(a->target->debug_fd); return fdopen(fd,"w"); } return NULL; } /* This IS called from signal handler */ void debug_action_sigcall( #if ANSI_C struct debug_struct *a, const char * format, ... #else a, format, va_alist #endif ) #if !ANSI_C struct debug_struct *a; CONST char * format; va_dcl #endif { int count, pos; char * format_error = NULL; CONST char *s; struct format_elem A[MAX_ARGS]; va_list vl; if (!a->target) return; print_prefix(a); Va_start(vl, format); /* defined in defs.h */ count = parse_format_args (A,MAX_ARGS, format,vl, &format_error); if (format_error) { PUTC(a->target,'?'); PUTC(a->target,'\n'); for (s = format_error; *s; s++) { PUTC(a->target,*s); } PUTC(a->target,'\n'); } pos = -1; for (s = format; *s; s++) { SKIP_TO_ARG(a,s); pos++; SKIP_WIDTH(a->target,s); if (pos < 0 || pos >= count) { PUTC(a->target,'?'); PUTC(a->target,'O'); PUTC(a->target,'?'); break; } if (A[pos].format_chr != *s) { PUTC(a->target,'?'); PUTC(a->target,'f'); PUTC(a->target,'?'); break; } if (!handle_common(a->target,A,pos)) { PUTC(a->target,'?'); PUTC(a->target,'u'); PUTC(a->target,'?'); } } va_end(vl); } /* This IS called from signal handler -- used on panic(), returns largest debug_level */ int panic_dprint( #if ANSI_C const char * format, ... #else format, va_alist #endif ) #if !ANSI_C CONST char * format; va_dcl #endif { int level = 0; int count; char * format_error = NULL; struct format_elem A[MAX_ARGS]; struct debug_target *ptr; va_list vl; Va_start(vl, format); /* defined in defs.h */ count = parse_format_args (A,MAX_ARGS, format,vl, &format_error); for (ptr = debug_list; ptr; ptr= ptr-> next) { int i; CONST char *s; int pos; DEBUG_VAR(temp,__FILE__,""); temp.target = ptr; for (i = 0; i < ptr->match_count; i++) { if (ptr->match[i].level > level) level = ptr->match[i].level; } if (format_error) { PUTC(ptr,'?'); PUTC(ptr,'\n'); for (s = format_error; *s; s++) { PUTC(ptr,*s); } PUTC(ptr,'\n'); } pos = -1; for (s = format; *s; s++) { SKIP_TO_ARG((&temp),s); pos++; SKIP_WIDTH(ptr,s); if (pos < 0 || pos >= count) { PUTC(ptr,'?'); PUTC(ptr,'o'); PUTC(ptr,'?'); break; } if (A[pos].format_chr != *s) { PUTC(ptr,'?'); PUTC(ptr,'f'); PUTC(ptr,'?'); break; } if (!handle_common(ptr,A,pos)) { PUTC(ptr,'?'); PUTC(ptr,'u'); PUTC(ptr,'?'); } } } va_end(vl); return level; } /* * Local Variables: * mode:c * c-basic-offset:4 * buffer-file-coding-system: iso-8859-1 * End: */