feat(syslog): openlog/syslog/closelog/vsyslog/setlogmask

This commit is contained in:
2026-09-05 23:18:34 -04:00
parent fefd17241f
commit 9b576757cf
3 changed files with 802 additions and 0 deletions
+172
View File
@@ -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 */
+397
View File
@@ -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) */
+233
View File
@@ -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) */