#include <Security/debugsupport.h>
#include <Security/globalizer.h>
#include <cstdarg>
#include <ctype.h>
#define SYSLOG_NAMES // compile syslog name tables
#include <syslog.h>
#if !defined(USE_CXXABI)
#define USE_CXXABI 0 // only available in gcc3 >v1100
#endif
#if USE_CXXABI
# include <cxxabi.h> // for name demangling
#endif //USE_CXXABI
namespace Security {
namespace Debug {
#if defined(NDEBUG)
void Scope::operator () (const char *, ...) { }
#else // NDEBUG
void debug(const char *scope, const char *format, ...)
{
#if !defined(NDEBUG_STUBS)
va_list args;
va_start(args, format);
Target::get().message(scope, format, args);
va_end(args);
#endif
}
void vdebug(const char *scope, const char *format, va_list args)
{
#if !defined(NDEBUG_STUBS)
Target::get().message(scope, format, args);
#endif
}
void Scope::operator () (const char *format, ...)
{
#if !defined(NDEBUG_STUBS)
va_list args;
va_start(args, format);
Target::get().message(mScope, format, args);
va_end(args);
#endif
}
bool debugging(const char *scope)
{
#if !defined(NDEBUG_STUBS)
return Target::get().debugging(scope);
#else
return false;
#endif
}
bool dumping(const char *scope)
{
#if defined(NDEBUG_STUBS)
return false;
#else
return Target::get().dump(scope);
#endif
}
void dump(const char *format, ...)
{
#if !defined(NDEBUG_STUBS)
va_list args;
va_start(args, format);
Target::get().dump(format, args);
va_end(args);
#endif
}
void dumpData(const void *ptr, size_t size)
{
#if !defined(NDEBUG_STUBS)
const char *addr = reinterpret_cast<const char *>(ptr);
const char *end = addr + size;
bool isText = true;
for (const char *p = addr; p < end; p++)
if (!isprint(*p)) { isText = false; break; }
if (isText) {
dump("\"");
for (const char *p = addr; p < end; p++)
dump("%c", *p);
dump("\"");
} else {
dump("0x");
for (const char *p = addr; p < end; p++)
dump("%2.2x", static_cast<unsigned char>(*p));
}
#endif //NDEBUG_STUBS
}
void dumpData(const char *title, const void *ptr, size_t size)
{
#if !defined(NDEBUG_STUBS)
dump("%s: ", title);
dumpData(ptr, size);
dump("\n");
#endif //NDEBUG_STUBS
}
string makeTypeName(const type_info &type)
{
#if USE_CXXABI
int status;
char *cname = abi::__cxa_demangle(type.name(), NULL, NULL, &status);
string name = cname; ::free(cname); return name;
#else
return type.name(); #endif
}
#if !defined(NDEBUG_STUBS)
Target::Target()
: showScope(false), showThread(false), showPid(false),
sink(NULL)
{
if (singleton == NULL)
singleton = this;
if (!previousTerminator) previousTerminator = set_terminate(terminator);
}
Target::~Target()
{
}
void Target::message(const char *scope, const char *format, va_list args)
{
if (logSelector(scope)) {
char buffer[messageConstructionSize]; char *bufp = buffer;
if (showScope && scope) { if (const char *sep = strchr(scope, ',')) {
bufp += sprintf(bufp, "%-*s", Name::maxLength, (const char *)Name(scope, sep));
} else { bufp += sprintf(bufp, "%-*s", Name::maxLength, scope);
}
}
if (showPid) { bufp += sprintf(bufp, "[%d] ", getpid());
}
if (showThread) { *bufp++ = '#';
Thread::Identity::current().getIdString(bufp);
bufp += strlen(bufp);
*bufp++ = ' ';
}
vsnprintf(bufp, buffer + sizeof(buffer) - bufp, format, args);
for (char *p = bufp; *p; p++)
if (!isprint(*p))
*p = '?';
sink->put(buffer, bufp - buffer);
}
}
bool Target::debugging(const char *scope)
{
return logSelector(scope);
}
void Target::dump(const char *format, va_list args)
{
char buffer[messageConstructionSize]; vsnprintf(buffer, sizeof(buffer), format, args);
for (char *p = buffer; *p; p++)
if (!isprint(*p) && !isspace(*p) || *p == '\r')
*p = '?';
sink->dump(buffer);
}
bool Target::dump(const char *scope)
{
return dumpSelector(scope);
}
Target::Selector::Selector() : useSet(false), negate(false)
{ }
void Target::Selector::operator = (const char *scope)
{
if (scope) {
if (!strcmp(scope, "all")) {
useSet = false;
negate = true;
} else if (!strcmp(scope, "none")) {
useSet = negate = false;
} else {
useSet = true;
enableSet.erase(enableSet.begin(), enableSet.end());
if (scope[0] == '-') {
negate = true;
scope++;
} else
negate = false;
while (const char *sep = strchr(scope, ',')) {
enableSet.insert(Name(scope, sep));
scope = sep + 1;
}
enableSet.insert(scope);
}
} else {
useSet = negate = false;
}
}
bool Target::Selector::operator () (const char *scope) const
{
if (scope == NULL)
return true;
if (useSet) {
while (const char *sep = strchr(scope, ',')) {
if (enableSet.find(Name(scope, sep)) != enableSet.end())
return !negate;
scope = sep + 1;
}
return (enableSet.find(scope) != enableSet.end()) != negate;
} else {
return negate;
}
}
void Target::setFromEnvironment()
{
logSelector = getenv("DEBUGSCOPE");
dumpSelector = getenv("DEBUGDUMP");
if (const char *dest = getenv("DEBUGDEST")) {
if (dest[0] == '/') { to(dest);
} else if (!strncmp(dest, "LOG_", 4)) { int facility = LOG_DAEMON;
for (CODE *cp = facilitynames; cp->c_name; cp++)
if (!strcmp(dest, cp->c_name))
facility = cp->c_val;
to(facility | LOG_DEBUG);
} else if (!strncmp(dest, ">&", 2)) { int fd = atoi(dest+2);
if (FILE *f = fdopen(fd, "a")) {
to(f);
} else {
to(stderr);
::debug(NULL, "cannot log to fd[%d]: %s", fd, strerror(errno));
}
} else { to(dest);
}
} else { to(stderr);
}
configure();
}
void Target::configure()
{
configure(getenv("DEBUGOPTIONS"));
}
void Target::configure(const char *config)
{
showScope = config && strstr(config, "scope");
showThread = config && strstr(config, "thread");
showPid = config && strstr(config, "pid");
if (sink)
sink->configure(config);
}
void Target::to(Sink *s)
{
delete sink;
sink = s;
}
void Target::to(FILE *file)
{
to(new FileSink(file));
}
void Target::to(const char *filename)
{
if (FILE *f = fopen(filename, "a")) {
to(new FileSink(f));
} else {
to(stderr);
::debug(NULL, "cannot debug to \"%s\": %s", filename, strerror(errno));
}
}
void Target::to(int syslogPriority)
{
to(new SyslogSink(syslogPriority));
}
Target *Target::singleton;
Target &Target::get()
{
if (singleton == NULL) {
Target *t = new Target;
t->setFromEnvironment();
}
return *singleton;
}
Target::Sink::~Sink()
{ }
void Target::Sink::dump(const char *)
{ }
void Target::Sink::configure(const char *)
{ }
terminate_handler Target::previousTerminator;
void Target::terminator()
{
debug("exception", "uncaught exception terminates program");
previousTerminator();
debug("exception", "prior termination handler failed to abort; forcing abort");
abort();
}
void FileSink::put(const char *buffer, unsigned int)
{
StLock<Mutex> locker(lock, false);
if (lockIO)
locker.lock();
if (addDate) {
time_t now = time(NULL);
char *date = ctime(&now);
date[19] = '\0';
fprintf(file, "%s ", date + 4); }
fputs(buffer, file);
putc('\n', file);
}
void FileSink::dump(const char *text)
{
StLock<Mutex> locker(lock, false);
if (lockIO)
locker.lock();
fputs(text, file);
}
void FileSink::configure(const char *options)
{
if (options == NULL || !strstr(options, "noflush"))
setlinebuf(file);
if (options) {
addDate = strstr(options, "date");
lockIO = !strstr(options, "nolock");
}
}
void SyslogSink::put(const char *buffer, unsigned int)
{
syslog(priority, "%s", buffer);
}
void SyslogSink::dump(const char *text)
{
snprintf(dumpPtr, dumpBuffer + dumpBufferSize - dumpPtr, "%s", text);
char *p = dumpBase;
while (char *q = strchr(p, '\n')) {
*q++ = '\0'; syslog(priority, " @@ %s", p);
p = q;
}
if (*p) { dumpPtr = p + strlen(p);
if ((dumpBase = p) > dumpBuffer + dumpBufferSize / 2) {
memmove(dumpBuffer, dumpBase, dumpPtr - dumpBase);
dumpPtr -= (dumpBase - dumpBuffer);
dumpBase = dumpBuffer;
}
} else { dumpBase = dumpPtr = dumpBuffer;
}
}
void SyslogSink::configure(const char *options)
{
}
#endif //NDEBUG_STUBS
#endif // NDEBUG
}
}