@@ -114,6 +114,8 @@ tests-special += $(objpfx)tst-error1-mem.out \
$(objpfx)tst-allocate_once-mem.out
endif
+tests-container := tst-syslog
+
CFLAGS-select.c += -fexceptions -fasynchronous-unwind-tables
CFLAGS-tsearch.c += $(uses-callbacks)
CFLAGS-lsearch.c += $(uses-callbacks)
new file mode 100644
@@ -0,0 +1,477 @@
+/* Basic tests for syslog interfaces.
+ Copyright (C) 2021 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it 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.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <netinet/in.h>
+#include <support/capture_subprocess.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xsocket.h>
+#include <support/xunistd.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <syslog.h>
+#include <sys/un.h>
+
+static const int facilities[] =
+ {
+ LOG_KERN,
+ LOG_USER,
+ LOG_MAIL,
+ LOG_DAEMON,
+ LOG_AUTH,
+ LOG_SYSLOG,
+ LOG_LPR,
+ LOG_NEWS,
+ LOG_UUCP,
+ LOG_CRON,
+ LOG_AUTHPRIV,
+ LOG_FTP,
+ LOG_LOCAL0,
+ LOG_LOCAL1,
+ LOG_LOCAL2,
+ LOG_LOCAL3,
+ LOG_LOCAL4,
+ LOG_LOCAL5,
+ LOG_LOCAL6,
+ LOG_LOCAL7,
+ };
+
+static const int priorities[] =
+ {
+ LOG_EMERG,
+ LOG_ALERT,
+ LOG_CRIT,
+ LOG_ERR,
+ LOG_WARNING,
+ LOG_NOTICE,
+ LOG_INFO,
+ LOG_DEBUG
+ };
+
+enum
+ {
+ ident_length = 64,
+ msg_length = 64
+ };
+
+#define SYSLOG_MSG_BASE "syslog_message"
+#define OPENLOG_IDENT "openlog_ident"
+
+struct msg_t
+ {
+ int priority;
+ int facility;
+ char ident[ident_length];
+ char msg[msg_length];
+ pid_t pid;
+ };
+
+static void
+call_vsyslog (int priority, const char *format, ...)
+{
+ va_list ap;
+ va_start (ap, format);
+ vsyslog (priority, format, ap);
+ va_end (ap);
+}
+
+static void
+send_vsyslog (int options)
+{
+ for (size_t i = 0; i < array_length (facilities); i++)
+ {
+ for (size_t j = 0; j < array_length (priorities); j++)
+ {
+ int facility = facilities[i];
+ int priority = priorities[j];
+ call_vsyslog (facility | priority, "%s %d %d", SYSLOG_MSG_BASE,
+ facility, priority);
+ }
+ }
+}
+
+static void
+send_syslog (int options)
+{
+ for (size_t i = 0; i < array_length (facilities); i++)
+ {
+ for (size_t j = 0; j < array_length (priorities); j++)
+ {
+ int facility = facilities[i];
+ int priority = priorities[j];
+ syslog (facility | priority, "%s %d %d", SYSLOG_MSG_BASE, facility,
+ priority);
+ }
+ }
+}
+
+static bool
+check_syslog_message (const struct msg_t *msg, int msgnum, int options,
+ pid_t pid)
+{
+ if (msgnum == array_length (facilities) * array_length (priorities) - 1)
+ return false;
+
+ int i = msgnum / array_length (priorities);
+ int j = msgnum % array_length (priorities);
+
+ int expected_facility = facilities[i];
+ /* With no preceding openlog, syslog default to LOG_USER. */
+ if (expected_facility == LOG_KERN)
+ expected_facility = LOG_USER;
+ int expected_priority = priorities[j];
+
+ TEST_COMPARE (msg->facility, expected_facility);
+ TEST_COMPARE (msg->priority, expected_priority);
+
+ return true;
+}
+
+static void
+send_openlog (int options)
+{
+ /* Define a non-default IDENT and a not default facility. */
+ openlog (OPENLOG_IDENT, options, LOG_LOCAL0);
+ for (size_t j = 0; j < array_length (priorities); j++)
+ {
+ int priority = priorities[j];
+ syslog (priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_LOCAL0, priority);
+ }
+ closelog ();
+
+ /* Back to the default IDENT with a non default facility. */
+ openlog (NULL, 0, LOG_LOCAL6);
+ for (size_t j = 0; j < array_length (priorities); j++)
+ {
+ int priority = priorities[j];
+ syslog (LOG_LOCAL7 | priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_LOCAL7,
+ priority);
+ }
+ closelog ();
+
+ /* LOG_KERN does not change the internal default facility. */
+ openlog (NULL, 0, LOG_KERN);
+ for (size_t j = 0; j < array_length (priorities); j++)
+ {
+ int priority = priorities[j];
+ syslog (priority, "%s %d %d", SYSLOG_MSG_BASE, LOG_KERN, priority);
+ }
+ closelog ();
+}
+
+static bool
+check_openlog_message (const struct msg_t *msg, int msgnum,
+ int options, pid_t pid)
+{
+ if (msgnum == 3 * array_length (priorities) - 1)
+ return false;
+
+ int expected_priority = priorities[msgnum % array_length (priorities)];
+ TEST_COMPARE (msg->priority, expected_priority);
+
+ char expected_ident[ident_length];
+ snprintf (expected_ident, sizeof (expected_ident), "%s%s%.0d%s:",
+ OPENLOG_IDENT,
+ options & LOG_PID ? "[" : "",
+ options & LOG_PID ? pid : 0,
+ options & LOG_PID ? "]" : "");
+
+ if (msgnum < array_length (priorities))
+ {
+ if (options & LOG_PID)
+ TEST_COMPARE (msg->pid, pid);
+ TEST_COMPARE_STRING (msg->ident, expected_ident);
+ TEST_COMPARE (msg->facility, LOG_LOCAL0);
+ }
+ else if (msgnum < 2 * array_length (priorities))
+ TEST_COMPARE (msg->facility, LOG_LOCAL7);
+ else if (msgnum < 3 * array_length (priorities))
+ TEST_COMPARE (msg->facility, LOG_KERN);
+
+ return true;
+}
+
+static struct msg_t
+parse_syslog_msg (const char *msg)
+{
+ struct msg_t r = { .pid = -1 };
+ int number;
+
+ /* The message in the form:
+ <179>Apr 8 14:51:19 tst-syslog: syslog message 176 3 */
+ int n = sscanf (msg, "<%3d>%*s %*d %*d:%*d:%*d %32s %64s %*d %*d",
+ &number, r.ident, r.msg);
+ TEST_COMPARE (n, 3);
+
+ r.facility = number & LOG_FACMASK;
+ r.priority = number & LOG_PRIMASK;
+
+ char *pid_start = strchr (r.ident, '[');
+ if (pid_start != NULL)
+ {
+ char *pid_end = strchr (r.ident, ']');
+ if (pid_end != NULL)
+ r.pid = strtoul (pid_start + 1, NULL, 10);
+ }
+
+ return r;
+}
+
+static struct msg_t
+parse_syslog_console (const char *msg)
+{
+ int priority;
+ int facility;
+ struct msg_t r;
+
+ /* The message in the form:
+ openlog_ident: syslog_message 128 0 */
+ int n = sscanf (msg, "%32s %64s %d %d",
+ r.ident, r.msg, &facility, &priority);
+ TEST_COMPARE (n, 4);
+
+ r.facility = facility;
+ r.priority = priority;
+
+ return r;
+}
+
+static void
+check_syslog_udp (void (*syslog_send)(int), int options,
+ bool (*syslog_check)(const struct msg_t *, int, int,
+ pid_t))
+{
+ struct sockaddr_un addr =
+ {
+ .sun_family = AF_UNIX,
+ .sun_path = _PATH_LOG
+ };
+
+ socklen_t addrlen = sizeof (addr);
+ int server_udp = xsocket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ xbind (server_udp, (struct sockaddr *) &addr, addrlen);
+
+ pid_t sender_pid = xfork ();
+ if (sender_pid == 0)
+ {
+ syslog_send (options);
+ _exit (0);
+ }
+
+ int msgnum = 0;
+ while (1)
+ {
+ char buf[512];
+ size_t l = xrecvfrom (server_udp, buf, sizeof (buf), 0,
+ (struct sockaddr *) &addr, &addrlen);
+ buf[l] = '\0';
+
+ struct msg_t msg = parse_syslog_msg (buf);
+ if (!syslog_check (&msg, msgnum++, options, sender_pid))
+ break;
+ }
+
+ xclose (server_udp);
+
+ int status;
+ xwaitpid (sender_pid, &status, 0);
+ TEST_COMPARE (status, 0);
+
+ unlink (_PATH_LOG);
+}
+
+static void
+check_syslog_tcp (void (*syslog_send)(int), int options,
+ bool (*syslog_check)(const struct msg_t *, int, int,
+ pid_t))
+{
+ struct sockaddr_un addr =
+ {
+ .sun_family = AF_UNIX,
+ .sun_path = _PATH_LOG
+ };
+ socklen_t addrlen = sizeof (addr);
+
+ int server_tcp = xsocket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+ xbind (server_tcp, (struct sockaddr *) &addr, addrlen);
+ xlisten (server_tcp, 5);
+
+ pid_t sender_pid = xfork ();
+ if (sender_pid == 0)
+ {
+ syslog_send (options);
+ _exit (0);
+ }
+
+ int client_tcp = xaccept (server_tcp, NULL, NULL);
+
+ char buf[512], *rb = buf;
+ size_t rbl = sizeof (buf);
+ size_t prl = 0; /* Track the size of the partial record. */
+ int msgnum = 0;
+
+ while (1)
+ {
+ size_t rl = xrecvfrom (client_tcp, rb, rbl - prl, 0, NULL, NULL);
+ if (rl == 0)
+ break;
+
+ /* Iterate over the buffer to find and check the record. */
+ size_t l = rl + prl;
+ char *b = buf;
+ while (1)
+ {
+ /* With TCP each record ends with a '\0'. */
+ char *e = memchr (b, '\0', l);
+ if (e != NULL)
+ {
+ struct msg_t msg = parse_syslog_msg (b);
+ if (!syslog_check (&msg, msgnum++, options, sender_pid))
+ break;
+
+ /* Advance to the next record. */
+ ptrdiff_t diff = e + 1 - b;
+ b += diff;
+ l -= diff;
+ }
+ else
+ {
+ /* Move the partial record to the start of the buffer. */
+ memmove (buf, b, l);
+ rb = buf + l;
+ prl = l;
+ break;
+ }
+ }
+ }
+
+ xclose (client_tcp);
+ xclose (server_tcp);
+
+ int status;
+ xwaitpid (sender_pid, &status, 0);
+ TEST_COMPARE (status, 0);
+
+ unlink (_PATH_LOG);
+}
+
+static void
+check_syslog_console_read (FILE *fp)
+{
+ char buf[512];
+ int msgnum = 0;
+ while (fgets (buf, sizeof (buf), fp) != NULL)
+ {
+ struct msg_t msg = parse_syslog_console (buf);
+ TEST_COMPARE_STRING (msg.ident, OPENLOG_IDENT ":");
+ TEST_COMPARE (msg.priority, priorities[msgnum]);
+ TEST_COMPARE (msg.facility, LOG_LOCAL0);
+
+ if (++msgnum == array_length (priorities))
+ break;
+ }
+}
+
+static void
+check_syslog_console (void)
+{
+ xmkfifo (_PATH_CONSOLE, 0666);
+
+ pid_t sender_pid = xfork ();
+ if (sender_pid == 0)
+ {
+ send_openlog (LOG_CONS);
+ _exit (0);
+ }
+
+ {
+ FILE *fp = xfopen (_PATH_CONSOLE, "r+");
+ check_syslog_console_read (fp);
+ xfclose (fp);
+ }
+
+ int status;
+ xwaitpid (sender_pid, &status, 0);
+ TEST_COMPARE (status, 0);
+
+ unlink (_PATH_CONSOLE);
+}
+
+static void
+send_openlog_callback (void *clousure)
+{
+ int options = *(int *) clousure;
+ send_openlog (options);
+}
+
+static void
+check_syslog_perror (void)
+{
+ struct support_capture_subprocess result;
+ result = support_capture_subprocess (send_openlog_callback,
+ &(int){LOG_PERROR});
+
+ FILE *mfp = fmemopen (result.err.buffer, result.err.length, "r");
+ if (mfp == NULL)
+ FAIL_EXIT1 ("fmemopen: %m");
+ check_syslog_console_read (mfp);
+ xfclose (mfp);
+
+ support_capture_subprocess_check (&result, "tst-openlog-child", 0,
+ sc_allow_stderr);
+ support_capture_subprocess_free (&result);
+}
+
+static int
+do_test (void)
+{
+ add_temp_file (_PATH_LOG);
+ add_temp_file (_PATH_CONSOLE);
+
+ /* Send every combination of facility/priority over UDP and TCP. */
+ check_syslog_udp (send_syslog, 0, check_syslog_message);
+ check_syslog_tcp (send_syslog, 0, check_syslog_message);
+
+ /* Also check vsyslog. */
+ check_syslog_udp (send_vsyslog, 0, check_syslog_message);
+ check_syslog_tcp (send_vsyslog, 0, check_syslog_message);
+
+ /* Run some openlog/syslog/closelog combinations. */
+ check_syslog_udp (send_openlog, 0, check_openlog_message);
+ check_syslog_tcp (send_openlog, 0, check_openlog_message);
+
+ /* Check the LOG_PID option. */
+ check_syslog_udp (send_openlog, LOG_PID, check_openlog_message);
+ check_syslog_tcp (send_openlog, LOG_PID, check_openlog_message);
+
+ /* Check the LOG_CONS option. */
+ check_syslog_console ();
+
+ /* Check the LOG_PERROR option. */
+ check_syslog_perror ();
+
+ return 0;
+}
+
+#include <support/test-driver.c>