Coverage Report

Created: 2025-07-04 06:19

/src/nspr/pr/src/io/prlog.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "primpl.h"
8
#include "prenv.h"
9
#include "prprf.h"
10
#include <string.h>
11
#ifdef ANDROID
12
#  include <android/log.h>
13
#endif
14
15
/*
16
 * Lock used to lock the log.
17
 *
18
 * We can't define _PR_LOCK_LOG simply as PR_Lock because PR_Lock may
19
 * contain assertions.  We have to avoid assertions in _PR_LOCK_LOG
20
 * because PR_ASSERT calls PR_LogPrint, which in turn calls _PR_LOCK_LOG.
21
 * This can lead to infinite recursion.
22
 */
23
static PRLock* _pr_logLock;
24
#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
25
0
#  define _PR_LOCK_LOG() PR_Lock(_pr_logLock);
26
0
#  define _PR_UNLOCK_LOG() PR_Unlock(_pr_logLock);
27
#elif defined(_PR_GLOBAL_THREADS_ONLY)
28
#  define _PR_LOCK_LOG() \
29
    {                    \
30
      _PR_LOCK_LOCK(_pr_logLock)
31
#  define _PR_UNLOCK_LOG()        \
32
    _PR_LOCK_UNLOCK(_pr_logLock); \
33
    }
34
#else
35
36
#  define _PR_LOCK_LOG()                                \
37
    {                                                   \
38
      PRIntn _is;                                       \
39
      PRThread* _me = _PR_MD_CURRENT_THREAD();          \
40
      if (!_PR_IS_NATIVE_THREAD(_me)) _PR_INTSOFF(_is); \
41
      _PR_LOCK_LOCK(_pr_logLock)
42
43
#  define _PR_UNLOCK_LOG()                           \
44
    _PR_LOCK_UNLOCK(_pr_logLock);                    \
45
    PR_ASSERT(_me == _PR_MD_CURRENT_THREAD());       \
46
    if (!_PR_IS_NATIVE_THREAD(_me)) _PR_INTSON(_is); \
47
    }
48
49
#endif
50
51
#if defined(XP_PC)
52
#  define strcasecmp stricmp
53
#endif
54
55
/*
56
 * On NT, we can't define _PUT_LOG as PR_Write or _PR_MD_WRITE,
57
 * because every asynchronous file io operation leads to a fiber context
58
 * switch.  So we define _PUT_LOG as fputs (from stdio.h).  A side
59
 * benefit is that fputs handles the LF->CRLF translation.  This
60
 * code can also be used on other platforms with file stream io.
61
 */
62
#if defined(WIN32)
63
#  define _PR_USE_STDIO_FOR_LOGGING
64
#endif
65
66
/*
67
** Coerce Win32 log output to use OutputDebugString() when
68
** NSPR_LOG_FILE is set to "WinDebug".
69
*/
70
#if defined(XP_PC)
71
#  define WIN32_DEBUG_FILE (FILE*)-2
72
#endif
73
74
#ifdef WINCE
75
static void OutputDebugStringA(const char* msg) {
76
  int len = MultiByteToWideChar(CP_ACP, 0, msg, -1, 0, 0);
77
  WCHAR* wMsg = (WCHAR*)PR_Malloc(len * sizeof(WCHAR));
78
  MultiByteToWideChar(CP_ACP, 0, msg, -1, wMsg, len);
79
  OutputDebugStringW(wMsg);
80
  PR_Free(wMsg);
81
}
82
#endif
83
84
/* Macros used to reduce #ifdef pollution */
85
86
#if defined(_PR_USE_STDIO_FOR_LOGGING) && defined(XP_PC)
87
#  define _PUT_LOG(fd, buf, nb)        \
88
    PR_BEGIN_MACRO                     \
89
    if (logFile == WIN32_DEBUG_FILE) { \
90
      char savebyte = buf[nb];         \
91
      buf[nb] = '\0';                  \
92
      OutputDebugStringA(buf);         \
93
      buf[nb] = savebyte;              \
94
    } else {                           \
95
      fwrite(buf, 1, nb, fd);          \
96
      fflush(fd);                      \
97
    }                                  \
98
    PR_END_MACRO
99
#elif defined(_PR_USE_STDIO_FOR_LOGGING)
100
#  define _PUT_LOG(fd, buf, nb) \
101
    {                           \
102
      fwrite(buf, 1, nb, fd);   \
103
      fflush(fd);               \
104
    }
105
#elif defined(ANDROID)
106
#  define _PUT_LOG(fd, buf, nb)                            \
107
    PR_BEGIN_MACRO                                         \
108
    if (fd == _pr_stderr) {                                \
109
      char savebyte = buf[nb];                             \
110
      buf[nb] = '\0';                                      \
111
      __android_log_write(ANDROID_LOG_INFO, "PRLog", buf); \
112
      buf[nb] = savebyte;                                  \
113
    } else {                                               \
114
      PR_Write(fd, buf, nb);                               \
115
    }                                                      \
116
    PR_END_MACRO
117
#elif defined(_PR_PTHREADS)
118
0
#  define _PUT_LOG(fd, buf, nb) PR_Write(fd, buf, nb)
119
#else
120
#  define _PUT_LOG(fd, buf, nb) _PR_MD_WRITE(fd, buf, nb)
121
#endif
122
123
/************************************************************************/
124
125
static PRLogModuleInfo* logModules;
126
127
static char* logBuf = NULL;
128
static char* logp;
129
static char* logEndp;
130
#ifdef _PR_USE_STDIO_FOR_LOGGING
131
static FILE* logFile = NULL;
132
#else
133
static PRFileDesc* logFile = 0;
134
#endif
135
static PRBool outputTimeStamp = PR_FALSE;
136
static PRBool appendToLog = PR_FALSE;
137
138
0
#define LINE_BUF_SIZE 512
139
0
#define DEFAULT_BUF_SIZE 16384
140
141
#ifdef _PR_NEED_STRCASECMP
142
143
/*
144
 * strcasecmp is defined in /usr/ucblib/libucb.a on some platforms
145
 * such as NCR.  Linking with both libc and libucb
146
 * may cause some problem, so I just provide our own implementation
147
 * of strcasecmp here.
148
 */
149
150
static const unsigned char uc[] = {
151
    '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010',
152
    '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021',
153
    '\022', '\023', '\024', '\025', '\026', '\027', '\030', '\031', '\032',
154
    '\033', '\034', '\035', '\036', '\037', ' ',    '!',    '"',    '#',
155
    '$',    '%',    '&',    '\'',   '(',    ')',    '*',    '+',    ',',
156
    '-',    '.',    '/',    '0',    '1',    '2',    '3',    '4',    '5',
157
    '6',    '7',    '8',    '9',    ':',    ';',    '<',    '=',    '>',
158
    '?',    '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G',
159
    'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',    'P',
160
    'Q',    'R',    'S',    'T',    'U',    'V',    'W',    'X',    'Y',
161
    'Z',    '[',    '\\',   ']',    '^',    '_',    '`',    'A',    'B',
162
    'C',    'D',    'E',    'F',    'G',    'H',    'I',    'J',    'K',
163
    'L',    'M',    'N',    'O',    'P',    'Q',    'R',    'S',    'T',
164
    'U',    'V',    'W',    'X',    'Y',    'Z',    '{',    '|',    '}',
165
    '~',    '\177'};
166
167
PRIntn strcasecmp(const char* a, const char* b) {
168
  const unsigned char* ua = (const unsigned char*)a;
169
  const unsigned char* ub = (const unsigned char*)b;
170
171
  if (((const char*)0 == a) || (const char*)0 == b) {
172
    return (PRIntn)(a - b);
173
  }
174
175
  while ((uc[*ua] == uc[*ub]) && ('\0' != *a)) {
176
    a++;
177
    ua++;
178
    ub++;
179
  }
180
181
  return (PRIntn)(uc[*ua] - uc[*ub]);
182
}
183
184
#endif /* _PR_NEED_STRCASECMP */
185
186
12
void _PR_InitLog(void) {
187
12
  char* ev;
188
189
12
  _pr_logLock = PR_NewLock();
190
191
12
  ev = PR_GetEnv("NSPR_LOG_MODULES");
192
12
  if (ev && ev[0]) {
193
0
    char module[64]; /* Security-Critical: If you change this
194
                      * size, you must also change the sscanf
195
                      * format string to be size-1.
196
                      */
197
0
    PRBool isSync = PR_FALSE;
198
0
    PRIntn evlen = strlen(ev), pos = 0;
199
0
    PRInt32 bufSize = DEFAULT_BUF_SIZE;
200
0
    while (pos < evlen) {
201
0
      PRIntn level = 1, count = 0, delta = 0;
202
0
      count = sscanf(
203
0
          &ev[pos],
204
0
          "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
205
0
          "]%n:%d%n",
206
0
          module, &delta, &level, &delta);
207
0
      pos += delta;
208
0
      if (count == 0) {
209
0
        break;
210
0
      }
211
212
      /*
213
      ** If count == 2, then we got module and level. If count
214
      ** == 1, then level defaults to 1 (module enabled).
215
      */
216
0
      if (strcasecmp(module, "sync") == 0) {
217
0
        isSync = PR_TRUE;
218
0
      } else if (strcasecmp(module, "bufsize") == 0) {
219
0
        if (level >= LINE_BUF_SIZE) {
220
0
          bufSize = level;
221
0
        }
222
0
      } else if (strcasecmp(module, "timestamp") == 0) {
223
0
        outputTimeStamp = PR_TRUE;
224
0
      } else if (strcasecmp(module, "append") == 0) {
225
0
        appendToLog = PR_TRUE;
226
0
      } else {
227
0
        PRLogModuleInfo* lm = logModules;
228
0
        PRBool skip_modcheck =
229
0
            (0 == strcasecmp(module, "all")) ? PR_TRUE : PR_FALSE;
230
231
0
        while (lm != NULL) {
232
0
          if (skip_modcheck) {
233
0
            lm->level = (PRLogModuleLevel)level;
234
0
          } else if (strcasecmp(module, lm->name) == 0) {
235
0
            lm->level = (PRLogModuleLevel)level;
236
0
            break;
237
0
          }
238
0
          lm = lm->next;
239
0
        }
240
0
      }
241
      /*found:*/
242
0
      count = sscanf(&ev[pos], " , %n", &delta);
243
0
      pos += delta;
244
0
      if (count == EOF) {
245
0
        break;
246
0
      }
247
0
    }
248
0
    PR_SetLogBuffering(isSync ? 0 : bufSize);
249
250
0
    ev = PR_GetEnvSecure("NSPR_LOG_FILE");
251
0
    if (ev && ev[0]) {
252
0
      if (!PR_SetLogFile(ev)) {
253
#ifdef XP_PC
254
        char* str = PR_smprintf("Unable to create nspr log file '%s'\n", ev);
255
        if (str) {
256
          OutputDebugStringA(str);
257
          PR_smprintf_free(str);
258
        }
259
#else
260
0
        fprintf(stderr, "Unable to create nspr log file '%s'\n", ev);
261
0
#endif
262
0
      }
263
0
    } else {
264
#ifdef _PR_USE_STDIO_FOR_LOGGING
265
      logFile = stderr;
266
#else
267
0
      logFile = _pr_stderr;
268
0
#endif
269
0
    }
270
0
  }
271
12
}
272
273
0
void _PR_LogCleanup(void) {
274
0
  PRLogModuleInfo* lm = logModules;
275
276
0
  PR_LogFlush();
277
278
#ifdef _PR_USE_STDIO_FOR_LOGGING
279
  if (logFile && logFile != stdout && logFile != stderr
280
#  ifdef XP_PC
281
      && logFile != WIN32_DEBUG_FILE
282
#  endif
283
  ) {
284
    fclose(logFile);
285
  }
286
#else
287
0
  if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
288
0
    PR_Close(logFile);
289
0
  }
290
0
#endif
291
0
  logFile = NULL;
292
293
0
  if (logBuf) {
294
0
    PR_DELETE(logBuf);
295
0
  }
296
297
0
  while (lm != NULL) {
298
0
    PRLogModuleInfo* next = lm->next;
299
0
    free((/*const*/ char*)lm->name);
300
0
    PR_Free(lm);
301
0
    lm = next;
302
0
  }
303
0
  logModules = NULL;
304
305
0
  if (_pr_logLock) {
306
0
    PR_DestroyLock(_pr_logLock);
307
0
    _pr_logLock = NULL;
308
0
  }
309
0
}
310
311
132
static void _PR_SetLogModuleLevel(PRLogModuleInfo* lm) {
312
132
  char* ev;
313
314
132
  ev = PR_GetEnv("NSPR_LOG_MODULES");
315
132
  if (ev && ev[0]) {
316
0
    char module[64]; /* Security-Critical: If you change this
317
                      * size, you must also change the sscanf
318
                      * format string to be size-1.
319
                      */
320
0
    PRIntn evlen = strlen(ev), pos = 0;
321
0
    while (pos < evlen) {
322
0
      PRIntn level = 1, count = 0, delta = 0;
323
324
0
      count = sscanf(
325
0
          &ev[pos],
326
0
          "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
327
0
          "]%n:%d%n",
328
0
          module, &delta, &level, &delta);
329
0
      pos += delta;
330
0
      if (count == 0) {
331
0
        break;
332
0
      }
333
334
      /*
335
      ** If count == 2, then we got module and level. If count
336
      ** == 1, then level defaults to 1 (module enabled).
337
      */
338
0
      if (lm != NULL) {
339
0
        if ((strcasecmp(module, "all") == 0) ||
340
0
            (strcasecmp(module, lm->name) == 0)) {
341
0
          lm->level = (PRLogModuleLevel)level;
342
0
        }
343
0
      }
344
0
      count = sscanf(&ev[pos], " , %n", &delta);
345
0
      pos += delta;
346
0
      if (count == EOF) {
347
0
        break;
348
0
      }
349
0
    }
350
0
  }
351
132
} /* end _PR_SetLogModuleLevel() */
352
353
132
PR_IMPLEMENT(PRLogModuleInfo*) PR_NewLogModule(const char* name) {
354
132
  PRLogModuleInfo* lm;
355
356
132
  if (!_pr_initialized) {
357
0
    _PR_ImplicitInitialization();
358
0
  }
359
360
132
  lm = PR_NEWZAP(PRLogModuleInfo);
361
132
  if (lm) {
362
132
    lm->name = strdup(name);
363
132
    lm->level = PR_LOG_NONE;
364
132
    lm->next = logModules;
365
132
    logModules = lm;
366
132
    _PR_SetLogModuleLevel(lm);
367
132
  }
368
132
  return lm;
369
132
}
370
371
0
PR_IMPLEMENT(PRBool) PR_SetLogFile(const char* file) {
372
#ifdef _PR_USE_STDIO_FOR_LOGGING
373
  FILE* newLogFile;
374
375
#  ifdef XP_PC
376
  if (strcmp(file, "WinDebug") == 0) {
377
    newLogFile = WIN32_DEBUG_FILE;
378
  } else
379
#  endif
380
  {
381
    const char* mode = appendToLog ? "a" : "w";
382
    newLogFile = fopen(file, mode);
383
    if (!newLogFile) {
384
      return PR_FALSE;
385
    }
386
387
#  ifndef WINCE /* _IONBF does not exist in the Windows Mobile 6 SDK. */
388
    /* We do buffering ourselves. */
389
    setvbuf(newLogFile, NULL, _IONBF, 0);
390
#  endif
391
  }
392
  if (logFile && logFile != stdout && logFile != stderr
393
#  ifdef XP_PC
394
      && logFile != WIN32_DEBUG_FILE
395
#  endif
396
  ) {
397
    fclose(logFile);
398
  }
399
  logFile = newLogFile;
400
  return PR_TRUE;
401
#else
402
0
  PRFileDesc* newLogFile;
403
0
  PRIntn flags = PR_WRONLY | PR_CREATE_FILE;
404
0
  if (appendToLog) {
405
0
    flags |= PR_APPEND;
406
0
  } else {
407
0
    flags |= PR_TRUNCATE;
408
0
  }
409
410
0
  newLogFile = PR_Open(file, flags, 0666);
411
0
  if (newLogFile) {
412
0
    if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
413
0
      PR_Close(logFile);
414
0
    }
415
0
    logFile = newLogFile;
416
0
  }
417
0
  return (PRBool)(newLogFile != 0);
418
0
#endif /* _PR_USE_STDIO_FOR_LOGGING */
419
0
}
420
421
0
PR_IMPLEMENT(void) PR_SetLogBuffering(PRIntn buffer_size) {
422
0
  PR_LogFlush();
423
424
0
  if (logBuf) {
425
0
    PR_DELETE(logBuf);
426
0
  }
427
428
0
  if (buffer_size >= LINE_BUF_SIZE) {
429
0
    logp = logBuf = (char*)PR_MALLOC(buffer_size);
430
0
    logEndp = logp + buffer_size;
431
0
  }
432
0
}
433
434
0
PR_IMPLEMENT(void) PR_LogPrint(const char* fmt, ...) {
435
0
  va_list ap;
436
0
  char line[LINE_BUF_SIZE];
437
0
  char* line_long = NULL;
438
0
  PRUint32 nb_tid = 0, nb;
439
0
  PRThread* me;
440
0
  PRExplodedTime now;
441
442
0
  if (!_pr_initialized) {
443
0
    _PR_ImplicitInitialization();
444
0
  }
445
446
0
  if (!logFile) {
447
0
    return;
448
0
  }
449
450
0
  if (outputTimeStamp) {
451
0
    PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
452
0
    nb_tid = PR_snprintf(line, sizeof(line) - 1,
453
0
                         "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - ",
454
0
                         now.tm_year, now.tm_month + 1, now.tm_mday,
455
0
                         now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec);
456
0
  }
457
458
0
  me = PR_GetCurrentThread();
459
0
  nb_tid += PR_snprintf(line + nb_tid, sizeof(line) - nb_tid - 1, "%ld[%p]: ",
460
#if defined(_PR_BTHREADS)
461
                        me, me);
462
#else
463
0
                        me ? me->id : 0L, me);
464
0
#endif
465
466
0
  va_start(ap, fmt);
467
0
  nb = nb_tid + PR_vsnprintf(line + nb_tid, sizeof(line) - nb_tid - 1, fmt, ap);
468
0
  va_end(ap);
469
470
  /*
471
   * Check if we might have run out of buffer space (in case we have a
472
   * long line), and malloc a buffer just this once.
473
   */
474
0
  if (nb == sizeof(line) - 2) {
475
0
    va_start(ap, fmt);
476
0
    line_long = PR_vsmprintf(fmt, ap);
477
0
    va_end(ap);
478
    /* If this failed, we'll fall back to writing the truncated line. */
479
0
  }
480
481
0
  if (line_long) {
482
0
    nb = strlen(line_long);
483
0
    _PR_LOCK_LOG();
484
0
    if (logBuf != 0) {
485
0
      _PUT_LOG(logFile, logBuf, logp - logBuf);
486
0
      logp = logBuf;
487
0
    }
488
    /*
489
     * Write out the thread id (with an optional timestamp) and the
490
     * malloc'ed buffer.
491
     */
492
0
    _PUT_LOG(logFile, line, nb_tid);
493
0
    _PUT_LOG(logFile, line_long, nb);
494
    /* Ensure there is a trailing newline. */
495
0
    if (!nb || (line_long[nb - 1] != '\n')) {
496
0
      char eol[2];
497
0
      eol[0] = '\n';
498
0
      eol[1] = '\0';
499
0
      _PUT_LOG(logFile, eol, 1);
500
0
    }
501
0
    _PR_UNLOCK_LOG();
502
0
    PR_smprintf_free(line_long);
503
0
  } else {
504
    /* Ensure there is a trailing newline. */
505
0
    if (nb && (line[nb - 1] != '\n')) {
506
0
      line[nb++] = '\n';
507
0
      line[nb] = '\0';
508
0
    }
509
0
    _PR_LOCK_LOG();
510
0
    if (logBuf == 0) {
511
0
      _PUT_LOG(logFile, line, nb);
512
0
    } else {
513
      /* If nb can't fit into logBuf, write out logBuf first. */
514
0
      if (logp + nb > logEndp) {
515
0
        _PUT_LOG(logFile, logBuf, logp - logBuf);
516
0
        logp = logBuf;
517
0
      }
518
      /* nb is guaranteed to fit into logBuf. */
519
0
      memcpy(logp, line, nb);
520
0
      logp += nb;
521
0
    }
522
0
    _PR_UNLOCK_LOG();
523
0
  }
524
0
  PR_LogFlush();
525
0
}
526
527
0
PR_IMPLEMENT(void) PR_LogFlush(void) {
528
0
  if (logBuf && logFile) {
529
0
    _PR_LOCK_LOG();
530
0
    if (logp > logBuf) {
531
0
      _PUT_LOG(logFile, logBuf, logp - logBuf);
532
0
      logp = logBuf;
533
0
    }
534
0
    _PR_UNLOCK_LOG();
535
0
  }
536
0
}
537
538
0
PR_IMPLEMENT(void) PR_Abort(void) {
539
0
  PR_LogPrint("Aborting");
540
#ifdef ANDROID
541
  __android_log_write(ANDROID_LOG_ERROR, "PRLog", "Aborting");
542
#endif
543
0
  abort();
544
0
}
545
546
0
PR_IMPLEMENT(void) PR_Assert(const char* s, const char* file, PRIntn ln) {
547
0
  PR_LogPrint("Assertion failure: %s, at %s:%d\n", s, file, ln);
548
0
  fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln);
549
0
  fflush(stderr);
550
#ifdef WIN32
551
  DebugBreak();
552
#elif defined(ANDROID)
553
  __android_log_assert(NULL, "PRLog", "Assertion failure: %s, at %s:%d\n", s,
554
                       file, ln);
555
#endif
556
0
  abort();
557
0
}