blob: 47c6ffc9412b0c5198d93dc0e86db5c6f1a424b7 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef __ANDROID__
#include <android/log.h>
#endif /* __ANDROID__ */
#include "log.h"
#include "log_history.h"
#include "strfmt.h"
#include "file.h"
#include "unicode.h"
#include "utf8.h"
#include "timing.h"
#include "addresses.h"
#include "stdiox.h"
#include "io_misc.h"
#include "thread.h"
const char logCategoryName_all[] = "all";
const char logCategoryPrefix_disable = '-';
const char *const logLevelNames[] = {
"emergency", "alert", "critical", "error",
"warning", "notice", "information", "debug"
};
const unsigned int logLevelCount = ARRAY_COUNT(logLevelNames);
unsigned char systemLogLevel = LOG_NOTICE;
unsigned char stderrLogLevel = LOG_NOTICE;
typedef struct {
const char *name;
const char *title;
const char *prefix;
} LogCategoryEntry;
static const LogCategoryEntry logCategoryTable[LOG_CATEGORY_COUNT] = {
[LOG_CATEGORY_INDEX(INPUT_PACKETS)] = {
.name = "inpkts",
.title = strtext("Input Packets"),
.prefix = "input packet"
},
[LOG_CATEGORY_INDEX(OUTPUT_PACKETS)] = {
.name = "outpkts",
.title = strtext("Output Packets"),
.prefix = "output packet"
},
[LOG_CATEGORY_INDEX(BRAILLE_KEYS)] = {
.name = "brlkeys",
.title = strtext("Braille Key Events"),
.prefix = "braille key"
},
[LOG_CATEGORY_INDEX(KEYBOARD_KEYS)] = {
.name = "kbdkeys",
.title = strtext("Keyboard Key Events"),
.prefix = "keyboard key"
},
[LOG_CATEGORY_INDEX(CURSOR_TRACKING)] = {
.name = "csrtrk",
.title = strtext("Cursor Tracking"),
.prefix = "cursor tracking"
},
[LOG_CATEGORY_INDEX(CURSOR_ROUTING)] = {
.name = "csrrtg",
.title = strtext("Cursor Routing"),
.prefix = "cursor routing"
},
[LOG_CATEGORY_INDEX(UPDATE_EVENTS)] = {
.name = "update",
.title = strtext("Update Events"),
.prefix = "update"
},
[LOG_CATEGORY_INDEX(SPEECH_EVENTS)] = {
.name = "speech",
.title = strtext("Speech Events"),
.prefix = "speech"
},
[LOG_CATEGORY_INDEX(ASYNC_EVENTS)] = {
.name = "async",
.title = strtext("Async Events"),
.prefix = "async"
},
[LOG_CATEGORY_INDEX(SERVER_EVENTS)] = {
.name = "server",
.title = strtext("Server Events"),
.prefix = "server"
},
[LOG_CATEGORY_INDEX(GENERIC_IO)] = {
.name = "gio",
.title = strtext("generic I/O"),
.prefix = "GIO"
},
[LOG_CATEGORY_INDEX(SERIAL_IO)] = {
.name = "serial",
.title = strtext("Serial I/O"),
.prefix = "serial"
},
[LOG_CATEGORY_INDEX(USB_IO)] = {
.name = "usb",
.title = strtext("USB I/O"),
.prefix = "USB"
},
[LOG_CATEGORY_INDEX(BLUETOOTH_IO)] = {
.name = "bt",
.title = strtext("Bluetooth I/O"),
.prefix = "Bluetooth"
},
[LOG_CATEGORY_INDEX(HID_IO)] = {
.name = "hid",
.title = strtext("Human Interface I/O"),
.prefix = "HID"
},
[LOG_CATEGORY_INDEX(BRAILLE_DRIVER)] = {
.name = "brldrv",
.title = strtext("Braille Driver Events"),
.prefix = "braille driver"
},
[LOG_CATEGORY_INDEX(SPEECH_DRIVER)] = {
.name = "spkdrv",
.title = strtext("Speech Driver Events"),
.prefix = "speech driver"
},
[LOG_CATEGORY_INDEX(SCREEN_DRIVER)] = {
.name = "scrdrv",
.title = strtext("Screen Driver Events"),
.prefix = "screen driver"
},
};
unsigned char categoryLogLevel = LOG_WARNING;
unsigned char logCategoryFlags[LOG_CATEGORY_COUNT];
#if defined(WINDOWS)
static HANDLE windowsEventLog = INVALID_HANDLE_VALUE;
static WORD
toWindowsEventType (int level) {
if (level <= LOG_ERR) return EVENTLOG_ERROR_TYPE;
if (level <= LOG_WARNING) return EVENTLOG_WARNING_TYPE;
return EVENTLOG_INFORMATION_TYPE;
}
#elif defined(__MSDOS__)
#elif defined(__ANDROID__)
static int
toAndroidLogPriority (int level) {
switch (level) {
case LOG_EMERG: return ANDROID_LOG_FATAL;
case LOG_ALERT: return ANDROID_LOG_FATAL;
case LOG_CRIT: return ANDROID_LOG_FATAL;
case LOG_ERR: return ANDROID_LOG_ERROR;
case LOG_WARNING: return ANDROID_LOG_WARN;
case LOG_NOTICE: return ANDROID_LOG_INFO;
case LOG_INFO: return ANDROID_LOG_INFO;
case LOG_DEBUG: return ANDROID_LOG_DEBUG;
default: return ANDROID_LOG_UNKNOWN;
}
}
#elif defined(HAVE_SYSLOG_H)
static int syslogOpened = 0;
#endif /* system log internal definitions */
static LogEntry *logPrefixStack = NULL;
static FILE *logFile = NULL;
static inline const LogCategoryEntry *
getLogCategoryEntry (LogCategoryIndex index) {
return (index < LOG_CATEGORY_COUNT)? &logCategoryTable[index]: NULL;
}
const char *
getLogCategoryName (LogCategoryIndex index) {
const LogCategoryEntry *ctg = getLogCategoryEntry(index);
return (ctg && ctg->name)? ctg->name: "";
}
const char *
getLogCategoryTitle (LogCategoryIndex index) {
const LogCategoryEntry *ctg = getLogCategoryEntry(index);
return (ctg && ctg->title)? ctg->title: "";
}
static inline void
setLogCategoryFlag (const LogCategoryEntry *ctg, unsigned char state) {
logCategoryFlags[ctg - logCategoryTable] = state;
}
void
disableAllLogCategories (void) {
const LogCategoryEntry *ctg = logCategoryTable;
const LogCategoryEntry *end = ctg + LOG_CATEGORY_COUNT;
while (ctg < end) setLogCategoryFlag(ctg++, 0);
}
int
setLogCategory (const char *name) {
const LogCategoryEntry *ctg = logCategoryTable;
const LogCategoryEntry *end = ctg + LOG_CATEGORY_COUNT;
int on = 1;
int all;
if (*name == logCategoryPrefix_disable) {
on = 0;
name += 1;
}
all = strcasecmp(name, logCategoryName_all) == 0;
while (ctg < end) {
if (all || (ctg->name && (strcasecmp(name, ctg->name) == 0))) {
setLogCategoryFlag(ctg, on);
if (!all) return 1;
}
ctg += 1;
}
return all;
}
int
pushLogPrefix (const char *prefix) {
if (!prefix) prefix = "";
return pushLogEntry(&logPrefixStack, prefix, 0);
}
int
popLogPrefix (void) {
return popLogEntry(&logPrefixStack);
}
void
closeLogFile (void) {
if (logFile) {
fclose(logFile);
logFile = NULL;
}
}
void
openLogFile (const char *path) {
closeLogFile();
FILE *stream = fopen(path, "w");
if (stream) {
//setCloseOnExec(fileno(stream), 1);
writeUtf8ByteOrderMark(stream);
logFile = stream;
}
}
static void
writeLogRecord (const char *record) {
if (logFile) {
lockStream(logFile);
{
TimeValue now;
char buffer[0X20];
size_t length;
unsigned int milliseconds;
getCurrentTime(&now);
length = formatSeconds(buffer, sizeof(buffer), "%Y-%m-%d@%H:%M:%S", now.seconds);
milliseconds = now.nanoseconds / NSECS_PER_MSEC;
fprintf(logFile, "%.*s.%03u ", (int)length, buffer, milliseconds);
}
{
char name[0X40];
size_t length = formatThreadName(name, sizeof(name));
if (length) fprintf(logFile, "[%s] ", name);
}
fputs(record, logFile);
fputc('\n', logFile);
flushStream(logFile);
unlockStream(logFile);
}
}
void
openSystemLog (void) {
#if defined(WINDOWS)
if (windowsEventLog == INVALID_HANDLE_VALUE) {
windowsEventLog = RegisterEventSource(NULL, PACKAGE_TARNAME);
}
#elif defined(__MSDOS__)
if (!logFile) {
char *path = makeWritablePath(PACKAGE_TARNAME ".log");
if (path) {
openLogFile(path);
free(path);
}
}
#elif defined(__ANDROID__)
#elif defined(HAVE_SYSLOG_H)
if (!syslogOpened) {
openlog(PACKAGE_TARNAME, LOG_PID, LOG_DAEMON);
syslogOpened = 1;
}
#endif /* open system log */
}
void
closeSystemLog (void) {
#if defined(WINDOWS)
if (windowsEventLog != INVALID_HANDLE_VALUE) {
DeregisterEventSource(windowsEventLog);
windowsEventLog = INVALID_HANDLE_VALUE;
}
#elif defined(__MSDOS__)
closeLogFile();
#elif defined(__ANDROID__)
#elif defined(HAVE_SYSLOG_H)
if (syslogOpened) {
closelog();
syslogOpened = 0;
}
#endif /* close system log */
}
int
logData (int level, LogDataFormatter *formatLogData, const void *data) {
LogCategoryIndex category = level >> LOG_LEVEL_WIDTH;
level &= LOG_LEVEL_MASK;
const char *prefix;
int push;
if (category) {
category -= 1;
if (!logCategoryFlags[category]) return 0;
if (!level) level = categoryLogLevel;
const LogCategoryEntry *ctg = &logCategoryTable[category];
prefix = ctg->prefix;
push = 0;
} else {
prefix = NULL;
push = level <= LOG_WARNING;
}
int write = level <= systemLogLevel;
int print = level <= stderrLogLevel;
if (!(write || print || push)) return 0;
int oldErrno = errno;
char record[0X1000];
STR_BEGIN(record, sizeof(record));
if (prefix) STR_PRINTF("%s: ", prefix);
STR_FORMAT(formatLogData, data);
STR_END;
if (write) {
writeLogRecord(record);
#if defined(WINDOWS)
if (windowsEventLog != INVALID_HANDLE_VALUE) {
const char *strings[] = {record};
ReportEvent(
windowsEventLog, toWindowsEventType(level), 0, 0, NULL,
ARRAY_COUNT(strings), 0, strings, NULL
);
}
#elif defined(__MSDOS__)
#elif defined(__ANDROID__)
__android_log_write(
toAndroidLogPriority(level), PACKAGE_TARNAME, record
);
#elif defined(HAVE_SYSLOG_H)
if (syslogOpened) syslog(level, "%s", record);
#endif /* write system log */
}
if (print) {
FILE *stream = stderr;
lockStream(stream);
if (logPrefixStack) {
const char *prefix = getLogEntryText(logPrefixStack);
if (*prefix) {
fputs(prefix, stream);
fputs(": ", stream);
}
}
writeWithConsoleEncoding(stream, record, strlen(record));
fputc('\n', stream);
flushStream(stream);
unlockStream(stream);
}
if (push) pushLogMessage(record);
errno = oldErrno;
return 1;
}
static size_t
formatLogArguments (char *buffer, size_t size, const char *format, va_list *arguments) {
int length = vsnprintf(buffer, size, format, *arguments);
if (length < 0) return 0;
if (length < size) return length;
return size;
}
typedef struct {
const char *format;
va_list *arguments;
} LogMessageData;
static size_t
formatLogMessageData (char *buffer, size_t size, const void *data) {
const LogMessageData *msg = data;
return formatLogArguments(buffer, size, msg->format, msg->arguments);
}
int
vlogMessage (int level, const char *format, va_list *arguments) {
const LogMessageData msg = {
.format = format,
.arguments = arguments
};
return logData(level, formatLogMessageData, &msg);
}
int
logMessage (int level, const char *format, ...) {
int wasLogged;
va_list arguments;
va_start(arguments, format);
wasLogged = vlogMessage(level, format, &arguments);
va_end(arguments);
return wasLogged;
}
typedef struct {
const char *label;
va_list *arguments;
const void *data;
size_t length;
} LogBytesData;
static
STR_BEGIN_FORMATTER(formatLogBytesData, const void *data)
const LogBytesData *bytes = data;
const unsigned char *byte = bytes->data;
const unsigned char *end = byte + bytes->length;
if (bytes->label) {
STR_FORMAT(formatLogArguments, bytes->label, bytes->arguments);
STR_PRINTF(": ");
}
while (byte < end) {
if (byte != bytes->data) STR_PRINTF(" ");
STR_PRINTF("%2.2X", *byte++);
}
STR_END_FORMATTER
int
logBytes (int level, const char *label, const void *data, size_t length, ...) {
int wasLogged;
va_list arguments;
va_start(arguments, length);
{
const LogBytesData bytes = {
.label = label,
.arguments = &arguments,
.data = data,
.length = length
};
wasLogged = logData(level, formatLogBytesData, &bytes);
}
va_end(arguments);
return wasLogged;
}
typedef struct {
void *address;
const char *format;
va_list *arguments;
} LogSymbolData;
static
STR_BEGIN_FORMATTER(formatLogSymbolData, const void *data)
const LogSymbolData *symbol = data;
ptrdiff_t offset = 0;
const char *name = getAddressName(symbol->address, &offset);
STR_FORMAT(formatLogArguments, symbol->format, symbol->arguments);
STR_PRINTF(": ");
if (name && *name) {
STR_PRINTF("%s", name);
if (offset) STR_PRINTF("+%"PRIXPTR, (uintptr_t)offset);
} else {
STR_PRINTF("%p", symbol->address);
}
STR_END_FORMATTER
int
logSymbol (int level, void *address, const char *format, ...) {
int wasLogged;
va_list arguments;
va_start(arguments, format);
{
const LogSymbolData symbol = {
.address = address,
.format = format,
.arguments = &arguments
};
wasLogged = logData(level, formatLogSymbolData, &symbol);
}
va_end(arguments);
return wasLogged;
}
int
logActionProblem (int level, int error, const char *action) {
return logMessage(level, "%s error %d: %s", action, error, strerror(error));
}
int
logActionError (int error, const char *action) {
return logActionProblem(LOG_ERR, error, action);
}
int
logSystemProblem (int level, const char *action) {
return logActionProblem(level, errno, action);
}
int
logSystemError (const char *action) {
return logSystemProblem(LOG_ERR, action);
}
int
logMallocError (void) {
return logSystemError("malloc");
}
int
logUnsupportedFeature (const char *name) {
return logMessage(LOG_WARNING, "feature not supported: %s", name);
}
int
logUnsupportedOperation (const char *name) {
errno = ENOSYS;
return logSystemError(name);
}
int
logPossibleCause (const char *cause) {
return logMessage(LOG_WARNING, "possible cause: %s", cause);
}
#ifdef WINDOWS
int
logWindowsError (DWORD error, const char *action) {
char *message;
DWORD count = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(char *)&message, 0, NULL);
if (count) {
char *end = strpbrk(message, "\r\n");
if (end) *end = 0;
} else {
message = "unknown";
}
int wasLogged = logMessage(LOG_ERR, "%s error %d: %s", action, (int)error, message);
if (count) LocalFree(message);
return wasLogged;
}
int
logWindowsSystemError (const char *action) {
DWORD error = GetLastError();
return logWindowsError(error, action);
}
#ifdef __MINGW32__
int
logWindowsSocketError (const char *action) {
DWORD error = WSAGetLastError();
return logWindowsError(error, action);
}
#endif /* __MINGW32__ */
#endif /* WINDOWS */
static int
logBacktraceString (const char *string) {
return logMessage(LOG_DEBUG, "backtrace: %s", string);
}
#if defined(HAVE_EXECINFO_H)
#include <execinfo.h>
int
logBacktrace (void) {
const int limit = 30;
void *frames[limit];
int count = backtrace(frames, limit);
if (count > 0) {
char **strings;
if ((strings = backtrace_symbols(frames, count))) {
char **string = strings;
char **end = string + count;
while (string < end) {
if (!logBacktraceString(*string)) return 0;
string += 1;
}
if (count == limit) {
if (!logBacktraceString("...")) return 0;
}
free(strings);
} else {
logSystemError("backtrace_symbols");
}
} else {
if (!logBacktraceString("no frames")) return 0;
}
return 1;
}
#else /* log backtrace */
int
logBacktrace (void) {
return logBacktraceString("not supported");
}
#endif /* log backtrace */