1N/A/*
1N/A * Copyright (c) 2000, 2001, 2003, 2004 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A */
1N/A
1N/A#include <sm/gen.h>
1N/ASM_RCSID("@(#)$Id: debug.c,v 1.32 2009/09/20 05:38:46 ca Exp $")
1N/A
1N/A/*
1N/A** libsm debugging and tracing
1N/A** For documentation, see debug.html.
1N/A*/
1N/A
1N/A#include <ctype.h>
1N/A#include <stdlib.h>
1N/A#if _FFR_DEBUG_PID_TIME
1N/A#include <unistd.h>
1N/A#include <time.h>
1N/A#endif /* _FFR_DEBUG_PID_TIME */
1N/A#include <setjmp.h>
1N/A#include <sm/io.h>
1N/A#include <sm/assert.h>
1N/A#include <sm/conf.h>
1N/A#include <sm/debug.h>
1N/A#include <sm/string.h>
1N/A#include <sm/varargs.h>
1N/A#include <sm/heap.h>
1N/A
1N/Astatic void sm_debug_reset __P((void));
1N/Astatic const char *parse_named_setting_x __P((const char *));
1N/A
1N/A/*
1N/A** Abstractions for printing trace messages.
1N/A*/
1N/A
1N/A/*
1N/A** The output file to which trace output is directed.
1N/A** There is a controversy over whether this variable
1N/A** should be process global or thread local.
1N/A** To make the interface more abstract, we've hidden the
1N/A** variable behind access functions.
1N/A*/
1N/A
1N/Astatic SM_FILE_T *SmDebugOutput = smioout;
1N/A
1N/A/*
1N/A** SM_DEBUG_FILE -- Returns current debug file pointer.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** current debug file pointer.
1N/A*/
1N/A
1N/ASM_FILE_T *
1N/Asm_debug_file()
1N/A{
1N/A return SmDebugOutput;
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_SETFILE -- Sets debug file pointer.
1N/A**
1N/A** Parameters:
1N/A** fp -- new debug file pointer.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** Sets SmDebugOutput.
1N/A*/
1N/A
1N/Avoid
1N/Asm_debug_setfile(fp)
1N/A SM_FILE_T *fp;
1N/A{
1N/A SmDebugOutput = fp;
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_CLOSE -- Close debug file pointer.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Side Effects:
1N/A** Closes SmDebugOutput.
1N/A*/
1N/A
1N/Avoid
1N/Asm_debug_close()
1N/A{
1N/A if (SmDebugOutput != NULL && SmDebugOutput != smioout)
1N/A {
1N/A sm_io_close(SmDebugOutput, SM_TIME_DEFAULT);
1N/A SmDebugOutput = NULL;
1N/A }
1N/A}
1N/A
1N/A/*
1N/A** SM_DPRINTF -- printf() for debug output.
1N/A**
1N/A** Parameters:
1N/A** fmt -- format for printf()
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/A#if _FFR_DEBUG_PID_TIME
1N/ASM_DEBUG_T SmDBGPidTime = SM_DEBUG_INITIALIZER("sm_trace_pid_time",
1N/A "@(#)$Debug: sm_trace_pid_time - print pid and time in debug $");
1N/A#endif /* _FFR_DEBUG_PID_TIME */
1N/A
1N/Avoid
1N/A#if SM_VA_STD
1N/Asm_dprintf(char *fmt, ...)
1N/A#else /* SM_VA_STD */
1N/Asm_dprintf(fmt, va_alist)
1N/A char *fmt;
1N/A va_dcl
1N/A#endif /* SM_VA_STD */
1N/A{
1N/A SM_VA_LOCAL_DECL
1N/A
1N/A if (SmDebugOutput == NULL)
1N/A return;
1N/A#if _FFR_DEBUG_PID_TIME
1N/A /* note: this is ugly if the output isn't a full line! */
1N/A if (sm_debug_active(&SmDBGPidTime, 1))
1N/A {
1N/A static char str[32] = "[1900-00-00/00:00:00] ";
1N/A struct tm *tmp;
1N/A time_t currt;
1N/A
1N/A currt = time((time_t *)0);
1N/A tmp = localtime(&currt);
1N/A snprintf(str, sizeof(str), "[%d-%02d-%02d/%02d:%02d:%02d] ",
1N/A 1900 + tmp->tm_year, /* HACK */
1N/A tmp->tm_mon + 1,
1N/A tmp->tm_mday,
1N/A tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
1N/A sm_io_fprintf(SmDebugOutput, SmDebugOutput->f_timeout,
1N/A "%ld: %s ", (long) getpid(), str);
1N/A }
1N/A#endif /* _FFR_DEBUG_PID_TIME */
1N/A
1N/A SM_VA_START(ap, fmt);
1N/A sm_io_vfprintf(SmDebugOutput, SmDebugOutput->f_timeout, fmt, ap);
1N/A SM_VA_END(ap);
1N/A}
1N/A
1N/A/*
1N/A** SM_DFLUSH -- Flush debug output.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Avoid
1N/Asm_dflush()
1N/A{
1N/A sm_io_flush(SmDebugOutput, SM_TIME_DEFAULT);
1N/A}
1N/A
1N/A/*
1N/A** This is the internal database of debug settings.
1N/A** The semantics of looking up a setting in the settings database
1N/A** are that the *last* setting specified in a -d option on the sendmail
1N/A** command line that matches a given SM_DEBUG structure is the one that is
1N/A** used. That is necessary to conform to the existing semantics of
1N/A** the sendmail -d option. We store the settings as a linked list in
1N/A** reverse order, so when we do a lookup, we take the *first* entry
1N/A** that matches.
1N/A*/
1N/A
1N/Atypedef struct sm_debug_setting SM_DEBUG_SETTING_T;
1N/Astruct sm_debug_setting
1N/A{
1N/A const char *ds_pattern;
1N/A unsigned int ds_level;
1N/A SM_DEBUG_SETTING_T *ds_next;
1N/A};
1N/ASM_DEBUG_SETTING_T *SmDebugSettings = NULL;
1N/A
1N/A/*
1N/A** We keep a linked list of SM_DEBUG structures that have been initialized,
1N/A** for use by sm_debug_reset.
1N/A*/
1N/A
1N/ASM_DEBUG_T *SmDebugInitialized = NULL;
1N/A
1N/Aconst char SmDebugMagic[] = "sm_debug";
1N/A
1N/A/*
1N/A** SM_DEBUG_RESET -- Reset SM_DEBUG structures.
1N/A**
1N/A** Reset all SM_DEBUG structures back to the uninitialized state.
1N/A** This is used by sm_debug_addsetting to ensure that references to
1N/A** SM_DEBUG structures that occur before sendmail processes its -d flags
1N/A** do not cause those structures to be permanently forced to level 0.
1N/A**
1N/A** Parameters:
1N/A** none.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A*/
1N/A
1N/Astatic void
1N/Asm_debug_reset()
1N/A{
1N/A SM_DEBUG_T *debug;
1N/A
1N/A for (debug = SmDebugInitialized;
1N/A debug != NULL;
1N/A debug = debug->debug_next)
1N/A {
1N/A debug->debug_level = SM_DEBUG_UNKNOWN;
1N/A }
1N/A SmDebugInitialized = NULL;
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_ADDSETTING_X -- add an entry to the database of debug settings
1N/A**
1N/A** Parameters:
1N/A** pattern -- a shell-style glob pattern (see sm_match).
1N/A** WARNING: the storage for 'pattern' will be owned by
1N/A** the debug package, so it should either be a string
1N/A** literal or the result of a call to sm_strdup_x.
1N/A** level -- a non-negative integer.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Exceptions:
1N/A** F:sm_heap -- out of memory
1N/A*/
1N/A
1N/Avoid
1N/Asm_debug_addsetting_x(pattern, level)
1N/A const char *pattern;
1N/A int level;
1N/A{
1N/A SM_DEBUG_SETTING_T *s;
1N/A
1N/A SM_REQUIRE(pattern != NULL);
1N/A SM_REQUIRE(level >= 0);
1N/A s = sm_malloc_x(sizeof(SM_DEBUG_SETTING_T));
1N/A s->ds_pattern = pattern;
1N/A s->ds_level = (unsigned int) level;
1N/A s->ds_next = SmDebugSettings;
1N/A SmDebugSettings = s;
1N/A sm_debug_reset();
1N/A}
1N/A
1N/A/*
1N/A** PARSE_NAMED_SETTING_X -- process a symbolic debug setting
1N/A**
1N/A** Parameters:
1N/A** s -- Points to a non-empty \0 or , terminated string,
1N/A** of which the initial character is not a digit.
1N/A**
1N/A** Returns:
1N/A** pointer to terminating \0 or , character.
1N/A**
1N/A** Exceptions:
1N/A** F:sm.heap -- out of memory.
1N/A**
1N/A** Side Effects:
1N/A** adds the setting to the database.
1N/A*/
1N/A
1N/Astatic const char *
1N/Aparse_named_setting_x(s)
1N/A const char *s;
1N/A{
1N/A const char *pat, *endpat;
1N/A int level;
1N/A
1N/A pat = s;
1N/A while (*s != '\0' && *s != ',' && *s != '.')
1N/A ++s;
1N/A endpat = s;
1N/A if (*s == '.')
1N/A {
1N/A ++s;
1N/A level = 0;
1N/A while (isascii(*s) && isdigit(*s))
1N/A {
1N/A level = level * 10 + (*s - '0');
1N/A ++s;
1N/A }
1N/A if (level < 0)
1N/A level = 0;
1N/A }
1N/A else
1N/A level = 1;
1N/A
1N/A sm_debug_addsetting_x(sm_strndup_x(pat, endpat - pat), level);
1N/A
1N/A /* skip trailing junk */
1N/A while (*s != '\0' && *s != ',')
1N/A ++s;
1N/A
1N/A return s;
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_ADDSETTINGS_X -- process a list of debug options
1N/A**
1N/A** Parameters:
1N/A** s -- a list of debug settings, eg the argument to the
1N/A** sendmail -d option.
1N/A**
1N/A** The syntax of the string s is as follows:
1N/A**
1N/A** <settings> ::= <setting> | <settings> "," <setting>
1N/A** <setting> ::= <categories> | <categories> "." <level>
1N/A** <categories> ::= [a-zA-Z_*?][a-zA-Z0-9_*?]*
1N/A**
1N/A** However, note that we skip over anything we don't
1N/A** understand, rather than report an error.
1N/A**
1N/A** Returns:
1N/A** none.
1N/A**
1N/A** Exceptions:
1N/A** F:sm.heap -- out of memory
1N/A**
1N/A** Side Effects:
1N/A** updates the database of debug settings.
1N/A*/
1N/A
1N/Avoid
1N/Asm_debug_addsettings_x(s)
1N/A const char *s;
1N/A{
1N/A for (;;)
1N/A {
1N/A if (*s == '\0')
1N/A return;
1N/A if (*s == ',')
1N/A {
1N/A ++s;
1N/A continue;
1N/A }
1N/A s = parse_named_setting_x(s);
1N/A }
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_LOADLEVEL -- Get activation level of the specified debug object.
1N/A**
1N/A** Parameters:
1N/A** debug -- debug object.
1N/A**
1N/A** Returns:
1N/A** Activation level of the specified debug object.
1N/A**
1N/A** Side Effects:
1N/A** Ensures that the debug object is initialized.
1N/A*/
1N/A
1N/Aint
1N/Asm_debug_loadlevel(debug)
1N/A SM_DEBUG_T *debug;
1N/A{
1N/A if (debug->debug_level == SM_DEBUG_UNKNOWN)
1N/A {
1N/A SM_DEBUG_SETTING_T *s;
1N/A
1N/A for (s = SmDebugSettings; s != NULL; s = s->ds_next)
1N/A {
1N/A if (sm_match(debug->debug_name, s->ds_pattern))
1N/A {
1N/A debug->debug_level = s->ds_level;
1N/A goto initialized;
1N/A }
1N/A }
1N/A debug->debug_level = 0;
1N/A initialized:
1N/A debug->debug_next = SmDebugInitialized;
1N/A SmDebugInitialized = debug;
1N/A }
1N/A return (int) debug->debug_level;
1N/A}
1N/A
1N/A/*
1N/A** SM_DEBUG_LOADACTIVE -- Activation level reached?
1N/A**
1N/A** Parameters:
1N/A** debug -- debug object.
1N/A** level -- level to check.
1N/A**
1N/A** Returns:
1N/A** true iff the activation level of the specified debug
1N/A** object >= level.
1N/A**
1N/A** Side Effects:
1N/A** Ensures that the debug object is initialized.
1N/A*/
1N/A
1N/Abool
1N/Asm_debug_loadactive(debug, level)
1N/A SM_DEBUG_T *debug;
1N/A int level;
1N/A{
1N/A return sm_debug_loadlevel(debug) >= level;
1N/A}