#include <security_utilities/debugsupport.h>
#include <security_utilities/globalizer.h>
#include <cstdarg>
#include <ctype.h>
#define SYSLOG_NAMES // compile syslog name tables
#include <syslog.h>
#include <cxxabi.h> // for name demangling
#include <mach-o/dyld.h> // for _NSGetExecutablePath
#include <limits.h>
#define ENABLE_SECTRACE 1
namespace Security {
namespace Debug {
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_CODE)
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_CODE)
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_CODE)
dump("%s: ", title);
dumpData(ptr, size);
dump("\n");
#endif //NDEBUG_STUBS
}
string makeTypeName(const type_info &type)
{
int status;
char *cname = abi::__cxa_demangle(type.name(), NULL, NULL, &status);
string name = !strncmp(cname, "Security::", 10) ? (cname + 10) :
!strncmp(cname, "std::", 5) ? (cname + 5) :
cname;
::free(cname); return name;
}
#if !defined(NDEBUG_CODE)
char Target::progName[maxProgNameLength + 1];
unsigned int Target::PerThread::lastUsed;
Target::Target()
: showScope(false), showThread(false), showProc(false), showDate(false),
sink(NULL)
{
if (singleton == NULL)
singleton = this;
if (!previousTerminator) previousTerminator = set_terminate(terminator);
char execPath[PATH_MAX];
uint32_t length = sizeof(execPath);
if (_NSGetExecutablePath(execPath, &length)) {
strcpy(progName, "unknown");
} else {
const char *p = strrchr(execPath, '/');
if (p)
p++;
else
p = execPath;
size_t plen = strlen(p);
if (plen > maxProgNameLength) p += plen - maxProgNameLength; strcpy(progName, p);
}
}
Target::~Target()
{
}
static void addScope(char *&bufp, const char *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);
}
}
void Target::message(const char *scope, const char *format, va_list args)
{
if (logSelector(scope)) {
char buffer[messageConstructionSize]; char *bufp = buffer;
if (showDate && sink->needsDate) {
time_t now = time(NULL);
char *date = ctime(&now);
date[19] = '\0';
bufp += sprintf(bufp, "%s ", date + 4); }
if (showScope && scope)
addScope(bufp, scope);
if (showProc || showThread) {
char sub[maxProgNameLength + 20];
unsigned plen = 0;
if (showProc && showThread)
plen = sprintf(sub, "%s[%d]", progName, getpid());
else if (showProc)
plen = sprintf(sub, "%s", progName);
else
plen = sprintf(sub, "[%d]", getpid());
unsigned int id = perThread().id;
if (id > 1)
plen += sprintf(sub + plen, ":%d", id);
if (plen <= procLength)
bufp += sprintf(bufp, "%-*s ", int(procLength), sub);
else
bufp += sprintf(bufp, "%s ", sub + plen - procLength);
}
if (showScopeRight && scope)
addScope(bufp, scope);
size_t left = buffer + sizeof(buffer) - bufp - 1; size_t written = vsnprintf(bufp, left, format, args);
for (char *p = bufp; *p; p++)
if (!isprint(*p))
*p = '?';
if (written >= left) { bufp += left;
strcpy(bufp - 3, "...");
} else
bufp += written;
bufp[0] = '\n';
bufp[1] = '\0';
sink->put(buffer, (unsigned int)(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);
secdebug("", "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)
{
showScopeRight = config && strstr(config, "rscope");
showScope = !showScopeRight && config && strstr(config, "scope");
showThread = config && (strstr(config, "thread") || strstr(config, "pid")); showProc = config && strstr(config, "proc");
showDate = config && strstr(config, "date");
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);
secdebug("", "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()
{
secdebug("exception", "uncaught exception terminates program");
previousTerminator();
secdebug("exception", "prior termination handler failed to abort; forcing abort");
abort();
}
void FileSink::put(const char *inbuf, unsigned int length)
{
fwrite(inbuf, 1, length + 1, file); }
void FileSink::dump(const char *text)
{
fputs(text, file);
}
void FileSink::configure(const char *options)
{
if (options == NULL || !strstr(options, "noflush")) {
if (file != stderr)
setlinebuf(file);
}
}
void SyslogSink::put(const char *buffer, unsigned int length)
{
syslog(priority, "%1.*s", length, 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_CODE
} }