blob: 8a79601499b115e96ea445c0563331a3d3621c65 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape Portable Runtime (NSPR).
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* IBM Corporation
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "primpl.h"
#include "prenv.h"
#include "prprf.h"
#include <string.h>
/*
* Lock used to lock the log.
*
* We can't define _PR_LOCK_LOG simply as PR_Lock because PR_Lock may
* contain assertions. We have to avoid assertions in _PR_LOCK_LOG
* because PR_ASSERT calls PR_LogPrint, which in turn calls _PR_LOCK_LOG.
* This can lead to infinite recursion.
*/
static PRLock *_pr_logLock;
#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
#define _PR_LOCK_LOG() PR_Lock(_pr_logLock);
#define _PR_UNLOCK_LOG() PR_Unlock(_pr_logLock);
#elif defined(_PR_GLOBAL_THREADS_ONLY)
#define _PR_LOCK_LOG() { _PR_LOCK_LOCK(_pr_logLock)
#define _PR_UNLOCK_LOG() _PR_LOCK_UNLOCK(_pr_logLock); }
#else
#define _PR_LOCK_LOG() \
{ \
PRIntn _is; \
PRThread *_me = _PR_MD_CURRENT_THREAD(); \
if (!_PR_IS_NATIVE_THREAD(_me)) \
_PR_INTSOFF(_is); \
_PR_LOCK_LOCK(_pr_logLock)
#define _PR_UNLOCK_LOG() \
_PR_LOCK_UNLOCK(_pr_logLock); \
PR_ASSERT(_me == _PR_MD_CURRENT_THREAD()); \
if (!_PR_IS_NATIVE_THREAD(_me)) \
_PR_INTSON(_is); \
}
#endif
#if defined(XP_PC)
#define strcasecmp stricmp
#endif
/*
* On NT, we can't define _PUT_LOG as PR_Write or _PR_MD_WRITE,
* because every asynchronous file io operation leads to a fiber context
* switch. So we define _PUT_LOG as fputs (from stdio.h). A side
* benefit is that fputs handles the LF->CRLF translation. This
* code can also be used on other platforms with file stream io.
*/
#if defined(WIN32) || defined(XP_OS2)
#define _PR_USE_STDIO_FOR_LOGGING
#endif
/*
** Coerce Win32 log output to use OutputDebugString() when
** NSPR_LOG_FILE is set to "WinDebug".
*/
#if defined(XP_PC)
#define WIN32_DEBUG_FILE (FILE*)-2
#endif
#ifdef WINCE
static void OutputDebugStringA(const char* msg) {
int len = MultiByteToWideChar(CP_ACP, 0, msg, -1, 0, 0);
WCHAR *wMsg = (WCHAR *)PR_Malloc(len * sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, msg, -1, wMsg, len);
OutputDebugStringW(wMsg);
PR_Free(wMsg);
}
#endif
/* Macros used to reduce #ifdef pollution */
#if defined(_PR_USE_STDIO_FOR_LOGGING) && defined(XP_PC)
#define _PUT_LOG(fd, buf, nb) \
PR_BEGIN_MACRO \
if (logFile == WIN32_DEBUG_FILE) { \
char savebyte = buf[nb]; \
buf[nb] = '\0'; \
OutputDebugStringA(buf); \
buf[nb] = savebyte; \
} else { \
fwrite(buf, 1, nb, fd); \
fflush(fd); \
} \
PR_END_MACRO
#elif defined(_PR_USE_STDIO_FOR_LOGGING)
#define _PUT_LOG(fd, buf, nb) {fwrite(buf, 1, nb, fd); fflush(fd);}
#elif defined(_PR_PTHREADS)
#define _PUT_LOG(fd, buf, nb) PR_Write(fd, buf, nb)
#else
#define _PUT_LOG(fd, buf, nb) _PR_MD_WRITE(fd, buf, nb)
#endif
/************************************************************************/
static PRLogModuleInfo *logModules;
static char *logBuf = NULL;
static char *logp;
static char *logEndp;
#ifdef _PR_USE_STDIO_FOR_LOGGING
static FILE *logFile = NULL;
#else
static PRFileDesc *logFile = 0;
#endif
static PRBool outputTimeStamp = PR_FALSE;
#define LINE_BUF_SIZE 512
#define DEFAULT_BUF_SIZE 16384
#ifdef _PR_NEED_STRCASECMP
/*
* strcasecmp is defined in /usr/ucblib/libucb.a on some platforms
* such as NCR and Unixware. Linking with both libc and libucb
* may cause some problem, so I just provide our own implementation
* of strcasecmp here.
*/
static const unsigned char uc[] =
{
'\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
'\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
'\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
'\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
' ', '!', '"', '#', '$', '%', '&', '\'',
'(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z', '{', '|', '}', '~', '\177'
};
PRIntn strcasecmp(const char *a, const char *b)
{
const unsigned char *ua = (const unsigned char *)a;
const unsigned char *ub = (const unsigned char *)b;
if( ((const char *)0 == a) || (const char *)0 == b )
return (PRIntn)(a-b);
while( (uc[*ua] == uc[*ub]) && ('\0' != *a) )
{
a++;
ua++;
ub++;
}
return (PRIntn)(uc[*ua] - uc[*ub]);
}
#endif /* _PR_NEED_STRCASECMP */
void _PR_InitLog(void)
{
char *ev;
_pr_logLock = PR_NewLock();
ev = PR_GetEnv("NSPR_LOG_MODULES");
if (ev && ev[0]) {
char module[64]; /* Security-Critical: If you change this
* size, you must also change the sscanf
* format string to be size-1.
*/
PRBool isSync = PR_FALSE;
PRIntn evlen = strlen(ev), pos = 0;
PRInt32 bufSize = DEFAULT_BUF_SIZE;
while (pos < evlen) {
PRIntn level = 1, count = 0, delta = 0;
count = sscanf(&ev[pos], "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-]%n:%d%n",
module, &delta, &level, &delta);
pos += delta;
if (count == 0) break;
/*
** If count == 2, then we got module and level. If count
** == 1, then level defaults to 1 (module enabled).
*/
if (strcasecmp(module, "sync") == 0) {
isSync = PR_TRUE;
} else if (strcasecmp(module, "bufsize") == 0) {
if (level >= LINE_BUF_SIZE) {
bufSize = level;
}
} else if (strcasecmp(module, "timestamp") == 0) {
outputTimeStamp = PR_TRUE;
} else {
PRLogModuleInfo *lm = logModules;
PRBool skip_modcheck =
(0 == strcasecmp (module, "all")) ? PR_TRUE : PR_FALSE;
while (lm != NULL) {
if (skip_modcheck) lm -> level = (PRLogModuleLevel)level;
else if (strcasecmp(module, lm->name) == 0) {
lm->level = (PRLogModuleLevel)level;
break;
}
lm = lm->next;
}
}
/*found:*/
count = sscanf(&ev[pos], " , %n", &delta);
pos += delta;
if (count == EOF) break;
}
PR_SetLogBuffering(isSync ? 0 : bufSize);
#ifdef XP_UNIX
if ((getuid() != geteuid()) || (getgid() != getegid())) {
return;
}
#endif /* XP_UNIX */
ev = PR_GetEnv("NSPR_LOG_FILE");
if (ev && ev[0]) {
if (!PR_SetLogFile(ev)) {
#ifdef XP_PC
char* str = PR_smprintf("Unable to create nspr log file '%s'\n", ev);
if (str) {
OutputDebugStringA(str);
PR_smprintf_free(str);
}
#else
fprintf(stderr, "Unable to create nspr log file '%s'\n", ev);
#endif
}
} else {
#ifdef _PR_USE_STDIO_FOR_LOGGING
logFile = stderr;
#else
logFile = _pr_stderr;
#endif
}
}
}
void _PR_LogCleanup(void)
{
PRLogModuleInfo *lm = logModules;
PR_LogFlush();
#ifdef _PR_USE_STDIO_FOR_LOGGING
if (logFile
&& logFile != stdout
&& logFile != stderr
#ifdef XP_PC
&& logFile != WIN32_DEBUG_FILE
#endif
) {
fclose(logFile);
}
#else
if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
PR_Close(logFile);
}
#endif
logFile = NULL;
if (logBuf)
PR_DELETE(logBuf);
while (lm != NULL) {
PRLogModuleInfo *next = lm->next;
free((/*const*/ char *)lm->name);
PR_Free(lm);
lm = next;
}
logModules = NULL;
if (_pr_logLock) {
PR_DestroyLock(_pr_logLock);
_pr_logLock = NULL;
}
}
static void _PR_SetLogModuleLevel( PRLogModuleInfo *lm )
{
char *ev;
ev = PR_GetEnv("NSPR_LOG_MODULES");
if (ev && ev[0]) {
char module[64]; /* Security-Critical: If you change this
* size, you must also change the sscanf
* format string to be size-1.
*/
PRIntn evlen = strlen(ev), pos = 0;
while (pos < evlen) {
PRIntn level = 1, count = 0, delta = 0;
count = sscanf(&ev[pos], "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-]%n:%d%n",
module, &delta, &level, &delta);
pos += delta;
if (count == 0) break;
/*
** If count == 2, then we got module and level. If count
** == 1, then level defaults to 1 (module enabled).
*/
if (lm != NULL)
{
if ((strcasecmp(module, "all") == 0)
|| (strcasecmp(module, lm->name) == 0))
{
lm->level = (PRLogModuleLevel)level;
}
}
count = sscanf(&ev[pos], " , %n", &delta);
pos += delta;
if (count == EOF) break;
}
}
} /* end _PR_SetLogModuleLevel() */
PR_IMPLEMENT(PRLogModuleInfo*) PR_NewLogModule(const char *name)
{
PRLogModuleInfo *lm;
if (!_pr_initialized) _PR_ImplicitInitialization();
lm = PR_NEWZAP(PRLogModuleInfo);
if (lm) {
lm->name = strdup(name);
lm->level = PR_LOG_NONE;
lm->next = logModules;
logModules = lm;
_PR_SetLogModuleLevel(lm);
}
return lm;
}
PR_IMPLEMENT(PRBool) PR_SetLogFile(const char *file)
{
#ifdef _PR_USE_STDIO_FOR_LOGGING
FILE *newLogFile;
#ifdef XP_PC
if ( strcmp( file, "WinDebug") == 0)
{
newLogFile = WIN32_DEBUG_FILE;
}
else
#endif
{
newLogFile = fopen(file, "w");
if (!newLogFile)
return PR_FALSE;
#ifndef WINCE /* _IONBF does not exist in the Windows Mobile 6 SDK. */
/* We do buffering ourselves. */
setvbuf(newLogFile, NULL, _IONBF, 0);
#endif
}
if (logFile
&& logFile != stdout
&& logFile != stderr
#ifdef XP_PC
&& logFile != WIN32_DEBUG_FILE
#endif
) {
fclose(logFile);
}
logFile = newLogFile;
return PR_TRUE;
#else
PRFileDesc *newLogFile;
newLogFile = PR_Open(file, PR_WRONLY|PR_CREATE_FILE|PR_TRUNCATE, 0666);
if (newLogFile) {
if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
PR_Close(logFile);
}
logFile = newLogFile;
}
return (PRBool) (newLogFile != 0);
#endif /* _PR_USE_STDIO_FOR_LOGGING */
}
PR_IMPLEMENT(void) PR_SetLogBuffering(PRIntn buffer_size)
{
PR_LogFlush();
if (logBuf)
PR_DELETE(logBuf);
if (buffer_size >= LINE_BUF_SIZE) {
logp = logBuf = (char*) PR_MALLOC(buffer_size);
logEndp = logp + buffer_size;
}
}
PR_IMPLEMENT(void) PR_LogPrint(const char *fmt, ...)
{
va_list ap;
char line[LINE_BUF_SIZE];
char *line_long = NULL;
PRUint32 nb_tid = 0, nb;
PRThread *me;
PRExplodedTime now;
if (!_pr_initialized) _PR_ImplicitInitialization();
if (!logFile) {
return;
}
if (outputTimeStamp) {
PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
nb_tid = PR_snprintf(line, sizeof(line)-1,
"%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - ",
now.tm_year, now.tm_month + 1, now.tm_mday,
now.tm_hour, now.tm_min, now.tm_sec,
now.tm_usec);
}
me = PR_GetCurrentThread();
nb_tid += PR_snprintf(line+nb_tid, sizeof(line)-nb_tid-1, "%ld[%p]: ",
#if defined(_PR_BTHREADS)
me, me);
#else
me ? me->id : 0L, me);
#endif
va_start(ap, fmt);
nb = nb_tid + PR_vsnprintf(line+nb_tid, sizeof(line)-nb_tid-1, fmt, ap);
va_end(ap);
/*
* Check if we might have run out of buffer space (in case we have a
* long line), and malloc a buffer just this once.
*/
if (nb == sizeof(line)-2) {
va_start(ap, fmt);
line_long = PR_vsmprintf(fmt, ap);
va_end(ap);
/* If this failed, we'll fall back to writing the truncated line. */
}
if (line_long) {
nb = strlen(line_long);
_PR_LOCK_LOG();
if (logBuf != 0) {
_PUT_LOG(logFile, logBuf, logp - logBuf);
logp = logBuf;
}
/*
* Write out the thread id (with an optional timestamp) and the
* malloc'ed buffer.
*/
_PUT_LOG(logFile, line, nb_tid);
_PUT_LOG(logFile, line_long, nb);
/* Ensure there is a trailing newline. */
if (!nb || (line_long[nb-1] != '\n')) {
char eol[2];
eol[0] = '\n';
eol[1] = '\0';
_PUT_LOG(logFile, eol, 1);
}
_PR_UNLOCK_LOG();
PR_smprintf_free(line_long);
} else {
/* Ensure there is a trailing newline. */
if (nb && (line[nb-1] != '\n')) {
line[nb++] = '\n';
line[nb] = '\0';
}
_PR_LOCK_LOG();
if (logBuf == 0) {
_PUT_LOG(logFile, line, nb);
} else {
/* If nb can't fit into logBuf, write out logBuf first. */
if (logp + nb > logEndp) {
_PUT_LOG(logFile, logBuf, logp - logBuf);
logp = logBuf;
}
/* nb is guaranteed to fit into logBuf. */
memcpy(logp, line, nb);
logp += nb;
}
_PR_UNLOCK_LOG();
}
PR_LogFlush();
}
PR_IMPLEMENT(void) PR_LogFlush(void)
{
if (logBuf && logFile) {
_PR_LOCK_LOG();
if (logp > logBuf) {
_PUT_LOG(logFile, logBuf, logp - logBuf);
logp = logBuf;
}
_PR_UNLOCK_LOG();
}
}
PR_IMPLEMENT(void) PR_Abort(void)
{
PR_LogPrint("Aborting");
abort();
}
PR_IMPLEMENT(void) PR_Assert(const char *s, const char *file, PRIntn ln)
{
PR_LogPrint("Assertion failure: %s, at %s:%d\n", s, file, ln);
#if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS)
fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln);
#endif
#ifdef WIN32
DebugBreak();
#endif
#ifdef XP_OS2
asm("int $3");
#endif
abort();
}