feat(syslog): openlog/syslog/closelog/vsyslog/setlogmask
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
#ifndef VLIBC_SYSLOG_H
|
||||
#define VLIBC_SYSLOG_H
|
||||
|
||||
/*
|
||||
* vlibc — <syslog.h>.
|
||||
*
|
||||
* The syslog(3) logging interface, writing BSD-syslog datagrams to the
|
||||
* /dev/log socket (see src/misc/syslog.c for the wire details). syslog is
|
||||
* NOT POSIX.1-2008 base — it is XSI [CX] (and historical BSD), so this
|
||||
* ENTIRE header is gated at `VLIBC_LEVEL >= 2`: at level 1 (onlyposix) it is
|
||||
* empty, exactly like <strings.h>.
|
||||
*
|
||||
* The interface has three parts:
|
||||
*
|
||||
* - facilities and severities, plus the option flags of openlog. The
|
||||
* facility constants carry the classic BSD values, which are the Linux
|
||||
* wire ABI for /dev/log: facility = code << 3, severity = 0..7, and the
|
||||
* datagram's numeric priority is facility | severity. The facility code
|
||||
* is passed on the wire, so these numbers must match what syslogd
|
||||
* expects and are therefore kernel-ABI facts, not invented values;
|
||||
*
|
||||
* - LOG_MASK / LOG_UPTO, the two macros that build the mask argument of
|
||||
* setlogmask (LOG_MASK is POSIX; LOG_UPTO is the classic convenience
|
||||
* form both musl and glibc provide);
|
||||
*
|
||||
* - openlog / syslog / closelog / setlogmask and the vsyslog entry point
|
||||
* the standard requires syslog to be implemented in terms of. None of
|
||||
* them carries an intent attribute: every one reads and writes global
|
||||
* logging state and performs I/O, so const/pure would be unsound.
|
||||
*
|
||||
* %m handling: inside the syslog format string, "%m" is replaced by the
|
||||
* strerror(errno) text (a syslog-specific feature, implemented in syslog.c —
|
||||
* NOT a printf conversion). All other conversions are the standard ones,
|
||||
* handled by the library's own vsnprintf.
|
||||
*
|
||||
* This header includes <vlibc/features.h> itself, so the gate below always
|
||||
* sees the configured VLIBC_LEVEL even when the caller included no vlibc
|
||||
* header first, and <stdarg.h> for va_list.
|
||||
*/
|
||||
|
||||
#include <vlibc/features.h>
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- openlog option flags (bit mask) ---- */
|
||||
|
||||
#define LOG_PID 0x01 /* include the calling process' PID in each message */
|
||||
#define LOG_CONS 0x02 /* on send failure, write the message to the console */
|
||||
#define LOG_ODELAY 0x04 /* delay opening the /dev/log socket until first use */
|
||||
#define LOG_NDELAY 0x08 /* open the /dev/log socket immediately in openlog */
|
||||
#define LOG_NOWAIT 0x10 /* do not wait for child processes (accepted, unused) */
|
||||
#define LOG_PERROR 0x20 /* also write each message to standard error */
|
||||
|
||||
/* ---- facility codes (code << 3; the wire value of the datagram) ---- */
|
||||
|
||||
#define LOG_KERN 0 /* kernel messages */
|
||||
#define LOG_USER (1 << 3) /* random user-level messages */
|
||||
#define LOG_MAIL (2 << 3) /* mail system */
|
||||
#define LOG_DAEMON (3 << 3) /* system daemons */
|
||||
#define LOG_AUTH (4 << 3) /* security/authorization messages */
|
||||
#define LOG_LPR (6 << 3) /* line printer subsystem */
|
||||
#define LOG_NEWS (7 << 3) /* network news subsystem */
|
||||
#define LOG_UUCP (8 << 3) /* UUCP subsystem */
|
||||
#define LOG_CRON (9 << 3) /* clock daemon */
|
||||
#define LOG_LOCAL0 (16 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL1 (17 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL2 (18 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL3 (19 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL4 (20 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL5 (21 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL6 (22 << 3) /* reserved for local use */
|
||||
#define LOG_LOCAL7 (23 << 3) /* reserved for local use */
|
||||
|
||||
/* ---- severities (the low three bits of the priority value) ---- */
|
||||
|
||||
#define LOG_EMERG 0 /* system is unusable */
|
||||
#define LOG_ALERT 1 /* action must be taken immediately */
|
||||
#define LOG_CRIT 2 /* critical conditions */
|
||||
#define LOG_ERR 3 /* error conditions */
|
||||
#define LOG_WARNING 4 /* warning conditions */
|
||||
#define LOG_NOTICE 5 /* normal but significant condition */
|
||||
#define LOG_INFO 6 /* informational */
|
||||
#define LOG_DEBUG 7 /* debug-level messages */
|
||||
|
||||
/* Bit field holding the facility part (bits 3..9) of a priority value. */
|
||||
#define LOG_FACMASK 0x03f8
|
||||
|
||||
/* Bit field holding the severity part (bits 0..2) of a priority value. */
|
||||
#define LOG_PRIMASK 0x07
|
||||
|
||||
/* A mask with exactly pri's bit set (pri is a LOG_* severity, 0..7). */
|
||||
#define LOG_MASK(pri) (1 << (pri))
|
||||
|
||||
/* A mask with every severity from LOG_EMERG through pri included. */
|
||||
#define LOG_UPTO(pri) ((1 << ((pri) + 1)) - 1)
|
||||
|
||||
/*
|
||||
* Open a connection to the system logger. ident, when non-NULL, is copied
|
||||
* (a caller-owned pointer is not retained) and prefixes every message; a
|
||||
* NULL ident logs messages with no tag. option ORs in the LOG_* option
|
||||
* flags; facility is one of the LOG_* facility constants and is used for
|
||||
* messages whose priority carries no facility of its own. With LOG_NDELAY
|
||||
* the /dev/log socket is opened here; otherwise (and by default) it is
|
||||
* opened lazily on the first syslog call, and a failure to open it is
|
||||
* silent. Prior state — the mask from setlogmask and the default facility
|
||||
* LOG_USER — persists.
|
||||
*
|
||||
* I/O and global state: no intent attribute.
|
||||
*/
|
||||
void
|
||||
openlog(const char *ident, int option, int facility);
|
||||
|
||||
/*
|
||||
* Log a message: build "<facility|severity>ident[pid]: message" and send it
|
||||
* as one datagram to /dev/log, subject to the setlogmask mask. severity is
|
||||
* the low three bits of priority; a facility encoded in priority overrides
|
||||
* the openlog facility, and an out-of-range (high-bit) facility is ignored,
|
||||
* falling back to the openlog facility. The format string is the printf
|
||||
* family's, with "%m" additionally meaning the strerror(errno) text. When
|
||||
* LOG_PERROR was passed to openlog the composed line also goes to standard
|
||||
* error; when the send fails and LOG_CONS was passed, it goes to the console
|
||||
* instead. A failed open or send never terminates the process.
|
||||
*
|
||||
* I/O and global state: no intent attribute.
|
||||
*/
|
||||
void
|
||||
syslog(int priority, const char *format, ...);
|
||||
|
||||
/*
|
||||
* Close the connection to the system logger: close the /dev/log socket if
|
||||
* one is open, and clear the ident and option state installed by openlog
|
||||
* (the facility and mask are retained). A later syslog reopens the socket
|
||||
* lazily.
|
||||
*
|
||||
* I/O and global state: no intent attribute.
|
||||
*/
|
||||
void
|
||||
closelog(void);
|
||||
|
||||
/*
|
||||
* The engine behind syslog: identical semantics, taking a va_list instead
|
||||
* of the variadic tail. Each message is formatted exactly once (through
|
||||
* vsnprintf into an internal buffer) before any send, so LOG_PERROR and the
|
||||
* datagram always carry the same text.
|
||||
*
|
||||
* I/O and global state: no intent attribute.
|
||||
*/
|
||||
void
|
||||
vsyslog(int priority, const char *format, va_list ap);
|
||||
|
||||
/*
|
||||
* Set the process' log priority mask to maskpri — a bit per severity where
|
||||
* bit LOG_MASK(severity) set means "log this severity" — and return the
|
||||
* previous mask. A mask of 0 suppresses every message. The mask applies to
|
||||
* the severity only: facility is never filtered.
|
||||
*/
|
||||
int
|
||||
setlogmask(int maskpri);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
#endif /* VLIBC_SYSLOG_H */
|
||||
@@ -0,0 +1,397 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
/*
|
||||
* vlibc — openlog/syslog/closelog/vsyslog/setlogmask.
|
||||
*
|
||||
* syslog is XSI/BSD, not POSIX.1-2008 base, so the whole file is gated at
|
||||
* level 2 (whole-file-L2 rule); at level 1 this TU compiles empty.
|
||||
*
|
||||
* Wire format: each message is one SOCK_DGRAM datagram to the /dev/log
|
||||
* socket, shaped like
|
||||
*
|
||||
* <PRI>ident[pid]: message
|
||||
*
|
||||
* where PRI is the three-digit decimal priority code = facility | severity
|
||||
* (the facility constants in <syslog.h> are already code << 3, so the OR is
|
||||
* the wire value), ident and [pid] appear only when openlog received a
|
||||
* non-NULL ident / the LOG_PID option, and the datagram carries no
|
||||
* timestamp — the syslog daemon adds its own (glibc sends none either). The
|
||||
* message text is the caller's format expanded by vsnprintf (the library's
|
||||
* own, from src/stdio), with one syslog-specific addition: "%m" is replaced
|
||||
* by the strerror(errno) text before vsnprintf sees the format, because the
|
||||
* printf core deliberately has no %m conversion.
|
||||
*
|
||||
* The socket is opened lazily on the first syslog call (or immediately when
|
||||
* openlog gets LOG_NDELAY) and closed by closelog. Every failure — a socket
|
||||
* that cannot be created, a sendto that bounces (no daemon listening gives
|
||||
* ECONNREFUSED at once for an unconnected datagram socket, so nothing here
|
||||
* blocks), a console that cannot be opened — is silent, per the POSIX rule
|
||||
* that syslog must not terminate the process. sendto takes the destination
|
||||
* address per call, so a daemon restart that closes the daemon's socket
|
||||
* needs no reconnect; a single reopen-on-ENOTCONN retry is kept as a guard
|
||||
* for sockets that were implicitly connected.
|
||||
*
|
||||
* This is process-global state, deliberately not thread-synchronized: the
|
||||
* POSIX text does not require syslog to be thread-safe, and matching the
|
||||
* reference implementations' unsynchronized behavior keeps the hot path
|
||||
* free of locks.
|
||||
*/
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/* The /dev/log address: the classic BSD syslog datagram sink on Linux. */
|
||||
#define LOG_SOCK_PATH "/dev/log"
|
||||
#define LOG_CONSOLE_PATH "/dev/console"
|
||||
|
||||
/* Kernel ABI constants this TU needs but no public header provides yet. */
|
||||
#define LOG_AF_UNIX 1 /* struct sockaddr family for AF_UNIX */
|
||||
#define LOG_SOCK_DGRAM 2 /* SOCK_DGRAM */
|
||||
|
||||
/*
|
||||
* struct sockaddr_un layout for Linux x86_64, declared locally: the socket
|
||||
* layer (todo 57) does not exist yet, and pulling in a system header would
|
||||
* break the clean-room, host-header-free build.
|
||||
*/
|
||||
struct log_sockaddr
|
||||
{
|
||||
unsigned short sun_family;
|
||||
char sun_path[108];
|
||||
};
|
||||
|
||||
/* Bounded copy of the openlog ident; 256 covers every real tag. */
|
||||
static char log_ident[256];
|
||||
|
||||
/* The openlog option flags (LOG_PID/LOG_CONS/...). */
|
||||
static int log_option;
|
||||
|
||||
/* The facility (code << 3) used when a message carries no facility of its
|
||||
* own; POSIX's default when openlog was never called is LOG_USER. */
|
||||
static int log_facility = LOG_USER;
|
||||
|
||||
/* The setlogmask mask: one bit per severity. Default: everything (0xff). */
|
||||
static int log_mask = LOG_UPTO(LOG_DEBUG);
|
||||
|
||||
/* The /dev/log socket, or -1 while closed. */
|
||||
static int log_fd = -1;
|
||||
|
||||
/*
|
||||
* Open the /dev/log datagram socket unless one is already open. Failures are
|
||||
* silent and leave log_fd at -1; the next message simply tries again.
|
||||
*/
|
||||
static void
|
||||
log_open(void)
|
||||
{
|
||||
long r;
|
||||
|
||||
if (log_fd >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
r = __syscall3(SYS_socket, LOG_AF_UNIX, LOG_SOCK_DGRAM, 0);
|
||||
if (r >= 0)
|
||||
{
|
||||
log_fd = (int)r;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the len bytes at s to standard error with a raw write; ignore errors. */
|
||||
static void
|
||||
log_write_stderr(const char *s, size_t len)
|
||||
{
|
||||
(void)__syscall3(SYS_write, 2, (long)s, (long)len);
|
||||
}
|
||||
|
||||
/*
|
||||
* LOG_CONS fallback: write the message to /dev/console. The console is opened
|
||||
* O_WRONLY|O_NOCTTY so a session without a controlling terminal is not given
|
||||
* one, and is closed again right away — the file is not kept.
|
||||
*/
|
||||
static void
|
||||
log_write_console(const char *s, size_t len)
|
||||
{
|
||||
long fd;
|
||||
|
||||
fd = __syscall4(SYS_openat, AT_FDCWD, (long)LOG_CONSOLE_PATH, (long)(O_WRONLY | O_NOCTTY), 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
(void)__syscall3(SYS_write, fd, (long)s, (long)len);
|
||||
(void)__syscall3(SYS_write, fd, (long)"\n", 1);
|
||||
(void)__syscall1(SYS_close, fd);
|
||||
}
|
||||
|
||||
/* Render v in decimal into dst; return the number of characters written. */
|
||||
static size_t
|
||||
log_put_dec(char *dst, unsigned long v)
|
||||
{
|
||||
char tmp[20];
|
||||
size_t n = 0;
|
||||
|
||||
do
|
||||
{
|
||||
tmp[n++] = (char)('0' + (int)(v % 10));
|
||||
v /= 10;
|
||||
} while (v != 0);
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
dst[i] = tmp[n - 1 - i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
openlog(const char *ident, int option, int facility)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (ident == NULL)
|
||||
{
|
||||
log_ident[0] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
for (i = 0; ident[i] != '\0' && i + 1 < sizeof(log_ident); i++)
|
||||
{
|
||||
log_ident[i] = ident[i];
|
||||
}
|
||||
log_ident[i] = '\0';
|
||||
}
|
||||
log_option = option;
|
||||
log_facility = facility;
|
||||
if ((log_option & LOG_NDELAY) != 0)
|
||||
{
|
||||
log_open();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
closelog(void)
|
||||
{
|
||||
if (log_fd >= 0)
|
||||
{
|
||||
(void)__syscall1(SYS_close, log_fd);
|
||||
log_fd = -1;
|
||||
}
|
||||
log_ident[0] = '\0';
|
||||
log_option = 0;
|
||||
}
|
||||
|
||||
int
|
||||
setlogmask(int maskpri)
|
||||
{
|
||||
int old = log_mask;
|
||||
|
||||
log_mask = maskpri;
|
||||
return old;
|
||||
}
|
||||
|
||||
void
|
||||
vsyslog(int priority, const char *format, va_list ap)
|
||||
{
|
||||
int errno_saved = errno;
|
||||
int pr = priority & LOG_PRIMASK;
|
||||
int facility = priority & LOG_FACMASK;
|
||||
char expanded[1024];
|
||||
char message[1024];
|
||||
char line[1400];
|
||||
char *o = line;
|
||||
const char *const oend = line + sizeof(line) - 1;
|
||||
const char *es = NULL; /* strerror text, resolved on first "%m" */
|
||||
size_t msglen;
|
||||
size_t linelen;
|
||||
long r;
|
||||
int retried = 0;
|
||||
|
||||
/* The mask filters on severity alone; facility never suppresses. */
|
||||
if ((log_mask & LOG_MASK(pr)) == 0)
|
||||
{
|
||||
errno = errno_saved;
|
||||
return;
|
||||
}
|
||||
if (facility == 0)
|
||||
{
|
||||
facility = log_facility;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand "%m" into the strerror text, doubling any "%" inside that text
|
||||
* so vsnprintf prints it literally; every other character — including
|
||||
* the other conversion specifiers and their argument parsing — passes
|
||||
* through untouched, so the va_list still lines up. "%%m" is a literal
|
||||
* "%m", not a substitution.
|
||||
*/
|
||||
{
|
||||
const char *src = format;
|
||||
char *dst = expanded;
|
||||
const char *const dstend = expanded + sizeof(expanded) - 1;
|
||||
|
||||
while (*src != '\0' && dst < dstend)
|
||||
{
|
||||
if (src[0] == '%' && src[1] == '%')
|
||||
{
|
||||
if (dst + 1 >= dstend)
|
||||
{
|
||||
break;
|
||||
}
|
||||
dst[0] = '%';
|
||||
dst[1] = '%';
|
||||
dst += 2;
|
||||
src += 2;
|
||||
}
|
||||
else if (src[0] == '%' && src[1] == 'm')
|
||||
{
|
||||
if (es == NULL)
|
||||
{
|
||||
es = strerror(errno_saved);
|
||||
}
|
||||
for (const char *q = es; *q != '\0' && dst < dstend - 1; q++)
|
||||
{
|
||||
if (*q == '%')
|
||||
{
|
||||
dst[0] = '%';
|
||||
dst[1] = '%';
|
||||
dst += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[0] = *q;
|
||||
dst++;
|
||||
}
|
||||
}
|
||||
src += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[0] = *src;
|
||||
dst++;
|
||||
src++;
|
||||
}
|
||||
}
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
msglen = (size_t)vsnprintf(message, sizeof(message), expanded, ap);
|
||||
|
||||
/* "<facility|severity>" — three digits, zero-padded (values <= 191). */
|
||||
{
|
||||
int code = facility | pr;
|
||||
|
||||
*o++ = '<';
|
||||
*o++ = (char)('0' + code / 100);
|
||||
*o++ = (char)('0' + (code / 10) % 10);
|
||||
*o++ = (char)('0' + code % 10);
|
||||
*o++ = '>';
|
||||
}
|
||||
|
||||
for (size_t i = 0; log_ident[i] != '\0' && o < oend; i++)
|
||||
{
|
||||
*o++ = log_ident[i];
|
||||
}
|
||||
|
||||
if ((log_option & LOG_PID) != 0)
|
||||
{
|
||||
long pid = __syscall0(SYS_getpid);
|
||||
char digits[20];
|
||||
size_t n;
|
||||
|
||||
if (pid < 0)
|
||||
{
|
||||
pid = 0;
|
||||
}
|
||||
n = log_put_dec(digits, (unsigned long)pid);
|
||||
if (o < oend)
|
||||
{
|
||||
*o++ = '[';
|
||||
}
|
||||
for (size_t i = 0; i < n && o < oend; i++)
|
||||
{
|
||||
*o++ = digits[i];
|
||||
}
|
||||
if (o < oend)
|
||||
{
|
||||
*o++ = ']';
|
||||
}
|
||||
}
|
||||
|
||||
if (o + 1 < oend)
|
||||
{
|
||||
*o++ = ':';
|
||||
*o++ = ' ';
|
||||
}
|
||||
if (msglen > sizeof(message) - 1)
|
||||
{
|
||||
msglen = sizeof(message) - 1;
|
||||
}
|
||||
for (size_t i = 0; i < msglen && o < oend; i++)
|
||||
{
|
||||
*o++ = message[i];
|
||||
}
|
||||
*o = '\0';
|
||||
linelen = (size_t)(o - line);
|
||||
|
||||
if ((log_option & LOG_PERROR) != 0)
|
||||
{
|
||||
log_write_stderr(line, linelen);
|
||||
log_write_stderr("\n", 1);
|
||||
}
|
||||
|
||||
if (log_fd < 0)
|
||||
{
|
||||
log_open();
|
||||
}
|
||||
if (log_fd >= 0)
|
||||
{
|
||||
struct log_sockaddr sa = {.sun_family = LOG_AF_UNIX, .sun_path = LOG_SOCK_PATH};
|
||||
|
||||
do
|
||||
{
|
||||
r = __syscall6(SYS_sendto, log_fd, (long)line, (long)linelen, 0, (long)&sa,
|
||||
(long)sizeof(sa));
|
||||
if (r >= 0 || retried != 0 || -r != ENOTCONN)
|
||||
{
|
||||
break;
|
||||
}
|
||||
/* Stale after a daemon restart: drop and recreate the socket once. */
|
||||
(void)__syscall1(SYS_close, log_fd);
|
||||
log_fd = -1;
|
||||
log_open();
|
||||
retried = 1;
|
||||
} while (log_fd >= 0);
|
||||
|
||||
if (r < 0 && (log_option & LOG_CONS) != 0)
|
||||
{
|
||||
log_write_console(line, linelen);
|
||||
}
|
||||
}
|
||||
else if ((log_option & LOG_CONS) != 0)
|
||||
{
|
||||
log_write_console(line, linelen);
|
||||
}
|
||||
|
||||
errno = errno_saved;
|
||||
}
|
||||
|
||||
void
|
||||
syslog(int priority, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
vsyslog(priority, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* vlibc — syslog.h test (todo 36).
|
||||
*
|
||||
* Coverage (what is observable without a syslog daemon or a /dev/log
|
||||
* reader):
|
||||
*
|
||||
* 1. mask semantics: setlogmask returns the PREVIOUS mask, starts from the
|
||||
* default LOG_UPTO(LOG_DEBUG) == 0xff, and a 0 mask suppresses every
|
||||
* severity (only the return value and the no-crash property are
|
||||
* observable here — see 4);
|
||||
* 2. basic call sequence openlog/syslog/closelog with %d and %m in the
|
||||
* format, plus a priority carrying an out-of-range high facility bit
|
||||
* (masked away to the openlog facility — the defined failure);
|
||||
* 3. every severity 0..7 goes through the mask-and-format path once;
|
||||
* 4. LOG_PERROR: with the option set, the composed line — including the
|
||||
* PID under LOG_PID and the strerror text of %m — is written to
|
||||
* standard error. The evidence run captures stderr (2>&1) and greps
|
||||
* for "<code>vlibc-test[pid]: ..." to confirm the wire format;
|
||||
* 5. repeated openlog/syslog/closelog cycles: each openlog(LOG_NDELAY)
|
||||
* opens the socket and each closelog closes it again (rc-level check —
|
||||
* a leaked descriptor would surface as a failing openlog only after
|
||||
* thousands of cycles, so the assertion is deliberately weak);
|
||||
* 6. -f mode: setlogmask(0) then syslog(LOG_DEBUG, ...) — suppressed, and
|
||||
* must not crash even though the message was filtered.
|
||||
*
|
||||
* No host headers: <syslog.h> is vlibc's own (-Iinclude shadows the system
|
||||
* one), errno comes from vlibc's <errno.h>, and every diagnostic goes
|
||||
* through the raw SYS_write helpers from <vlibc/internal/test.h>. Compiled
|
||||
* with -DVLIBC_LEVEL=2 so the whole (entirely L2) header is exercised.
|
||||
*
|
||||
* Not part of the library proper; compiled manually for this todo (the
|
||||
* tests/ + make check wiring is owned by a later todo).
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <syslog.h>
|
||||
|
||||
#include <vlibc/internal/test.h>
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/* The default mask the first setlogmask call must report back. */
|
||||
#define TEST_DEFAULT_MASK LOG_UPTO(LOG_DEBUG)
|
||||
|
||||
/*
|
||||
* This test must run FIRST: it observes the initial mask (0xff) that no
|
||||
* earlier setlogmask call has disturbed, then walks it down and restores it.
|
||||
*/
|
||||
static int
|
||||
test_mask_roundtrip(void)
|
||||
{
|
||||
/* Initial default: everything logged (LOG_UPTO(LOG_DEBUG) == 0xff). */
|
||||
TEST_ASSERT_EQ(setlogmask(LOG_MASK(LOG_ERR)), TEST_DEFAULT_MASK);
|
||||
|
||||
/* LOG_MASK(LOG_ERR) is 1<<3 == 8; the previous 0xff comes back. */
|
||||
TEST_ASSERT_EQ(setlogmask(LOG_MASK(LOG_INFO)), LOG_MASK(LOG_ERR));
|
||||
|
||||
/* A zero mask means "log nothing", and returns the previous mask. */
|
||||
TEST_ASSERT_EQ(setlogmask(0), LOG_MASK(LOG_INFO));
|
||||
|
||||
/* Restore the all-open default for every later test. */
|
||||
TEST_ASSERT_EQ(setlogmask(TEST_DEFAULT_MASK), 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_basic_sequence(void)
|
||||
{
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
openlog("vlibc-test", LOG_PID | LOG_NDELAY, LOG_USER);
|
||||
|
||||
/* Plain numeric conversion through the vsnprintf core. */
|
||||
syslog(LOG_INFO, "hello %d", 42);
|
||||
|
||||
/* %m expands to the strerror text of the current errno. */
|
||||
errno = ENOENT;
|
||||
syslog(LOG_ERR, "io failure: %m");
|
||||
|
||||
/* A facility bit above LOG_FACMASK is masked away (defined failure). */
|
||||
syslog(LOG_ERR | (1 << 12), "high facility bit masked");
|
||||
syslog(LOG_ERR | (1 << 20) | LOG_INFO, "very high facility bit masked");
|
||||
|
||||
closelog();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_every_severity(void)
|
||||
{
|
||||
int sev;
|
||||
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
openlog("vlibc-test", LOG_NDELAY, LOG_USER);
|
||||
for (sev = LOG_EMERG; sev <= LOG_DEBUG; sev++)
|
||||
{
|
||||
syslog(sev, "severity %d", sev);
|
||||
}
|
||||
closelog();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The wire-format path: under LOG_PERROR the composed line is written raw to
|
||||
* standard error. Nothing is asserted here (the test harness cannot capture
|
||||
* fd 2); the evidence run redirects stderr and greps for the expected text.
|
||||
*/
|
||||
static int
|
||||
test_perror_output(void)
|
||||
{
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
errno = ENOENT;
|
||||
openlog("vlibc-test", LOG_PERROR | LOG_PID | LOG_NDELAY, LOG_USER);
|
||||
syslog(LOG_ERR, "perror-check %d %m", 7);
|
||||
closelog();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
test_open_close_cycles(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
openlog("cycle", LOG_PID | LOG_NDELAY, LOG_USER);
|
||||
syslog(LOG_WARNING, "cycle %d", i);
|
||||
closelog();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Failure-mode complement of the mask tests: with the mask at 0 even the
|
||||
* most severe message is filtered before any formatting or output — the
|
||||
* observable contract is the return value and the absence of a crash.
|
||||
*/
|
||||
static int
|
||||
test_mask_zero_suppresses(void)
|
||||
{
|
||||
TEST_ASSERT_EQ(setlogmask(0), TEST_DEFAULT_MASK);
|
||||
openlog("vlibc-test", LOG_PERROR | LOG_NDELAY, LOG_USER);
|
||||
syslog(LOG_EMERG, "must be suppressed");
|
||||
syslog(LOG_DEBUG, "must be suppressed too");
|
||||
closelog();
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct vlibc_test tests[] = {
|
||||
{"mask-roundtrip", test_mask_roundtrip},
|
||||
{"basic-sequence", test_basic_sequence},
|
||||
{"every-severity", test_every_severity},
|
||||
{"perror-output", test_perror_output},
|
||||
{"open-close-cycles", test_open_close_cycles},
|
||||
{"mask-zero-suppresses", test_mask_zero_suppresses},
|
||||
};
|
||||
|
||||
/*
|
||||
* Own main (not TEST_MAIN): supports the -f failure mode. Everything else
|
||||
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
|
||||
*/
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const size_t count = sizeof tests / sizeof tests[0];
|
||||
size_t passed = 0;
|
||||
size_t i;
|
||||
|
||||
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||
{
|
||||
/* The failure QA: mask 0 must suppress even LOG_DEBUG without harm. */
|
||||
int before = vlibc_test_failures;
|
||||
|
||||
vlibc_test_say(1, "RUN mask-zero-debug: ");
|
||||
TEST_ASSERT_EQ(setlogmask(0), TEST_DEFAULT_MASK);
|
||||
syslog(LOG_DEBUG, "suppressed by zero mask");
|
||||
setlogmask(TEST_DEFAULT_MASK);
|
||||
if (vlibc_test_failures == before)
|
||||
{
|
||||
vlibc_test_say(1, "PASS\n");
|
||||
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
|
||||
return 0;
|
||||
}
|
||||
vlibc_test_say(1, "FAIL\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
int before = vlibc_test_failures;
|
||||
|
||||
vlibc_test_say(1, "RUN ");
|
||||
vlibc_test_say(1, tests[i].name);
|
||||
vlibc_test_say(1, ": ");
|
||||
if (tests[i].run() == 0 && vlibc_test_failures == before)
|
||||
{
|
||||
vlibc_test_say(1, "PASS\n");
|
||||
passed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
vlibc_test_say(1, "FAIL\n");
|
||||
}
|
||||
}
|
||||
|
||||
vlibc_test_say(1, "SUMMARY: ");
|
||||
vlibc_test_say_dec(1, (unsigned long)passed);
|
||||
vlibc_test_say(1, "/");
|
||||
vlibc_test_say_dec(1, (unsigned long)count);
|
||||
vlibc_test_say(1, " passed, ");
|
||||
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
|
||||
vlibc_test_say(1, " assertion failure(s)\n");
|
||||
|
||||
return passed == count ? 0 : 1;
|
||||
}
|
||||
|
||||
#else /* !VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/*
|
||||
* Level 1: <syslog.h> is entirely level 2 (XSI), so there is nothing to
|
||||
* run. Keep the TU compilable at any configured profile.
|
||||
*/
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
vlibc_test_say(1, "SKIP: <syslog.h> is level 2, not available here\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
Reference in New Issue
Block a user