/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ │ above copyright notice and this permission notice appear in all copies. │ │ │ │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/dce.h" #include "libc/errno.h" #include "libc/fmt/fmt.h" #include "libc/log/internal.h" #include "libc/sysv/consts/log.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/af.h" #include "libc/sysv/consts/sock.h" #include "libc/runtime/valist.h" #include "libc/stdio/stdio.h" #include "libc/calls/calls.h" #include "libc/str/str.h" #include "libc/time/struct/tm.h" #include "libc/time/time.h" #include "libc/calls/weirdtypes.h" #include "libc/sock/sock.h" #include "libc/sock/syslog.h" #include "libc/nt/runtime.h" #include "libc/nt/events.h" /* Note: log_facility should be initialized with LOG_USER by default, * but since LOG_USER is not a constant value, we cannot initialize it * here. Set it to -1 to force the public functions to call __initlog() * for the first time. */ static int log_facility = -1; static char log_ident[32]; static int log_opt; static int log_mask; static uint16_t log_id; /* Used for Windows EvtID */ static int64_t log_fd = -1; static const struct sockaddr_un log_addr = { AF_UNIX, "/dev/log" }; static void __initlog() { log_ident[0] = '\0'; log_opt = LOG_ODELAY; log_facility = LOG_USER; log_mask = LOG_PRI(0xff); // Initially use max verbosity log_fd = -1; } forceinline int is_lost_conn(int e) { return (e==ECONNREFUSED) || (e==ECONNRESET) || (e==ENOTCONN) || (e==EPIPE); } static void __openlog() { if (IsWindows()) { log_fd = RegisterEventSourceA(NULL, log_ident); } else { log_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); if (log_fd >= 0) { int rc = connect(log_fd, (void *)&log_addr, sizeof(log_addr)); if (rc < 0) { printf("ERR: connect(openlog) failed: %s (errno=%d)\n", strerror(errno), errno); } } } } /** * Generates a log message which will be distributed by syslogd * * Note: no errors are reported if an error occurred while logging * the message. * * @param priority is a bitmask containing the facility value and * the level value. If no facility value is ORed into priority, * then the default value set by openlog() is used. * it set to NULL, the program name is used. * Level is one of LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, * LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG * @param message the format of the message to be processed by vprintf() * @param ap the va_list of arguments to be applied to the message * @asyncsignalsafe */ void vsyslog(int priority, const char *message, va_list ap) { char timebuf[16]; /* Store formatted time */ time_t now; struct tm tm; char buf[1024]; int errno_save = errno; /* Save current errno */ int pid; int l, l2; int hlen; /* If LOG_CONS is specified, use to store the point in * the header message after the timestamp */ if (log_fd < 0) __openlog(); if (!(priority & LOG_FACMASK)) priority |= log_facility; /* Build the time string */ now = time(NULL); gmtime_r(&now, &tm); strftime(timebuf, sizeof(timebuf), "%b %e %T", &tm); pid = (log_opt & LOG_PID) ? getpid() : 0; /* This is a clever trick to optionally include "[]" * only if pid != 0. When pid==0, the while "[%.0d]" is skipped: * %s%.0d%s -> String, pidValue, String * Each of those %s: * - if pid == 0 -> !pid is true (1), so "[" + 1 points to the * NULL terminator after the "[". * - if pid != 0 -> !pid is false (0), so the string printed is * the "[". */ l = snprintf(buf, sizeof(buf), "<%d>%s ", priority, timebuf); hlen = l; l += snprintf(buf+l, sizeof(buf)-l, "%s%s%.0d%s: ", log_ident, "["+!pid, pid, "]"+!pid); errno = errno_save; /* Append user message */ l2 = vsnprintf(buf+l, sizeof(buf)-l, message, ap); if (l2 >= 0) { if (l2 >= sizeof(buf) - l) { l = sizeof(buf) - 1; } else { l += l2; } if (buf[l-1] != '\n') { buf[l++] = '\n'; } if (!IsWindows()) { /* Unix: * - First try to send it to syslogd * - If fails and LOG_CONS is provided, writes to /dev/console */ #if 0 if (send(log_fd, buf, l, 0) < 0 && (!is_lost_conn(errno) || connect(log_fd, (void *)&log_addr, sizeof(log_addr)) < 0 || send(log_fd, buf, l, 0) < 0) && (log_opt & LOG_CONS)) { int fd = open("/dev/console", O_WRONLY|O_NOCTTY); if (fd >= 0) { dprintf(fd, "%.*s", l-hlen, buf+hlen); close(fd); } } #else int rc = send(log_fd, buf, l, 0); if (rc < 0) { printf("ERR: send(1) failed: %s (errno=%d)\n", strerror(errno), errno); if (!is_lost_conn(errno)) { rc = connect(log_fd, (void *)&log_addr, sizeof(log_addr)); if (rc < 0) { printf("ERR: connect(syslog) failed: %s (errno=%d)\n", strerror(errno), errno); } else { rc = send(log_fd, buf, l, 0); if (rc < 0) { printf("ERR: send(2) failed: %s (errno=%d)\n", strerror(errno), errno); } } } } #endif } else { uint16_t evtType; uint32_t evtID; const char *bufArr[] = { &buf[hlen] }; /* Only print the message without time*/ /* Windows only have 3 usable event types * Event ID are not supported * For more information on message types and event IDs, see: * https://docs.microsoft.com/en-us/windows/win32/eventlog/event-identifiers */ priority &= LOG_PRIMASK; // Remove facility from the priority field if (priority == LOG_EMERG || priority == LOG_ALERT || priority == LOG_CRIT) { evtType = EVENTLOG_ERROR_TYPE; evtID = 0xe << 28 | (LOG_USER) << 16; } else if (priority == LOG_WARNING) { evtType = EVENTLOG_WARNING_TYPE; evtID = 0xa << 28 | (LOG_USER) << 16; } else { /* LOG_NOTICE, LOG_INFO, LOG_DEBUG */ evtType = EVENTLOG_INFORMATION_TYPE; evtID = 0x6 << 28 | (LOG_USER) << 16; } ReportEventA(log_fd, evtType, /* Derived from priority */ 0 /* Category unsupported */, evtID, /* Unsupported */ NULL, /* User SID */ 1, /* Number of strings */ 0, /* Raw data size */ bufArr, /* String(s) */ NULL /* Arguments */ ); ++log_id; } if (log_opt & LOG_PERROR) dprintf(2, "%.*s", l-hlen, buf+hlen); } } /** * Sets log priority mask * * Modifies the log priority mask that determines which calls to * syslog() may be logged. * Log priority values are LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, * LOG_WARNING, LOG_NOTICE, LOG_INFO, and LOG_DEBUG. * * @param mask the new priority mask to use by syslog() * @return the previous log priority mask * @asyncsignalsafe */ int setlogmask(int maskpri) { int ret; if (log_facility == -1) { __initlog(); } ret = log_mask; if (maskpri) log_mask = LOG_PRI(maskpri); return ret; } /** * Opens a connection to the system logger * * Calling this function before calling syslog() is optional and * only allow customizing the identity, options and facility of * the messages logged. * * @param ident a string that prepends every logged message. If * it set to NULL, the program name is used. * @param opt specifies flags which control the operation of openlog(). * Only the following flags are supported: * LOG_CONS = Write directly to the system console if there is * an error while sending to the system logger. * LOG_NDELAY = Open the connection with the system logger * immediately instead of waiting for the first * message to be logged. * LOG_ODELAY = The converse of LOG_NDELAY. * LOG_PERROR = Also log the message to stderr. * LOG_PID = Include the caller's PID with each message * @param facility specifies the default facitlity value that defines * what kind of program is logging the message. * Possible values are: LOG_AUTH, LOG_AUTHPRIV, * LOG_CRON, LOG_DAEMON, LOG_FTP, LOG_LOCAL0 through LOG_LOCAL7, * LOG_LPR, LOG_MAIL, LOG_NEWS, LOG_SYSLOG, LOG_USER (default), * LOG_UUCP. * @asyncsignalsafe */ void openlog(const char *ident, int opt, int facility) { size_t n; if (log_facility == -1) { __initlog(); } if (!ident) { ident = program_invocation_short_name; } n = strnlen(ident, sizeof(log_ident) - 1); memcpy(log_ident, ident, n); log_ident[n] = 0; log_opt = opt; log_facility = facility; log_id = 0; if ((opt & LOG_NDELAY) && log_fd<0) __openlog(); } /** * Generates a log message which will be distributed by syslogd * * Note: no errors are reported if an error occurred while logging * the message. * * @param priority is a bitmask containing the facility value and * the level value. If no facility value is ORed into priority, * then the default value set by openlog() is used. * it set to NULL, the program name is used. * Level is one of LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, * LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG * @param message the message formatted using the same rules as printf() * @asyncsignalsafe */ void syslog(int priority, const char *message, ...) { va_list ap; if (log_facility == -1) { __initlog(); } if (LOG_PRI(priority) <= log_mask) { va_start(ap, message); vsyslog(priority, message, ap); va_end(ap); } } /** * Closes the file descriptor being used to write to the system logger * * Use of closelog is optional * @asyncsignalsafe */ void closelog(void) { if (log_facility == -1) { __initlog(); } if (log_fd == -1) { return; } if (IsWindows()) { DeregisterEventSource(log_fd); } else { close(log_fd); } log_fd = -1; }