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 <hurtta+elm@posti.FMI.FI> (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:
 */


syntax highlighted by Code2HTML, v. 0.9.1