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