syslogd: add option to log to Linux kernel printk buffer

Why invent our own shared memory circular buffer when the kernel has a
perfectly fine one already?

This can be used as a smaller/simpler alternative to the syslogd IPC support
(as IPC shmem/klogd/logread aren't needed), while also allowing centralised
logging of everything (kernel messages, userspace bootup and syslog)
when used together with ttyprintk.

Notice that kernel 3.5+ is needed to store syslog facility in printk buffer,
otherwise only the priority is stored.

bloat-o-meter compared to IPC+klogd+logread:
function                                             old     new   delta
get_linux_version_code                                 -      84     +84
lbb_prepare                                           25      90     +65
applet_nameofs                                         6       -      -6
static.stdout@@GLIBC_2                                 8       -      -8
applet_names                                          23       9     -14
bb_msg_standard_output                                16       -     -16
init_sem                                              18       -     -18
xatoull_range                                         19       -     -19
overlapping_strcpy                                    21       -     -21
init_data                                             56      32     -24
applet_main                                           24       -     -24
main                                                 124      99     -25
full_write2_str                                       26       -     -26
error_exit                                            26       -     -26
bb_basename                                           30       -     -30
sem_up                                                32       -     -32
interrupted                                           35       -     -35
fflush_stdout_and_exit                                38       -     -38
bb_banner                                             46       -     -46
find_applet_by_name                                   59       -     -59
bb_signals_recursive_norestart                        90       -     -90
run_applet_no_and_exit                               104       -    -104
timestamp_and_log                                    651     523    -128
syslogd_main                                         798     581    -217
xstrtoull_range_sfx                                  267       -    -267
run_applet_and_exit                                  432       -    -432
klogd_main                                           490       -    -490
logread_main                                         508       -    -508
.rodata                                             1870     937    -933
bb_common_bufsiz1                                   8193       -   -8193
------------------------------------------------------------------------------
(add/remove: 2/26 grow/shrink: 1/6 up/down: 149/-11829)    Total: -11680 bytes

Signed-off-by: Peter Korsgaard <jacmet@sunsite.dk>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c
index 5854bcd..ad54e22 100644
--- a/sysklogd/syslogd.c
+++ b/sysklogd/syslogd.c
@@ -43,6 +43,9 @@
 //usage:     "\n	-f FILE		Use FILE as config (default:/etc/syslog.conf)"
 //usage:	)
 /* //usage:  "\n	-m MIN		Minutes between MARK lines (default:20, 0=off)" */
+//usage:	IF_FEATURE_KMSG_SYSLOG(
+//usage:     "\n	-K		Log to kernel printk buffer (use dmesg to read it)"
+//usage:	)
 //usage:
 //usage:#define syslogd_example_usage
 //usage:       "$ syslogd -R masterlog:514\n"
@@ -140,6 +143,10 @@
 ) \
 IF_FEATURE_SYSLOGD_CFG( \
 	logRule_t *log_rules; \
+) \
+IF_FEATURE_KMSG_SYSLOG( \
+	int kmsgfd; \
+	int primask; \
 )
 
 struct init_globals {
@@ -212,6 +219,7 @@
 	IF_FEATURE_IPC_SYSLOG(    OPTBIT_circularlog,)	// -C
 	IF_FEATURE_SYSLOGD_DUP(   OPTBIT_dup        ,)	// -D
 	IF_FEATURE_SYSLOGD_CFG(   OPTBIT_cfg        ,)	// -f
+	IF_FEATURE_KMSG_SYSLOG(   OPTBIT_kmsg       ,)	// -K
 
 	OPT_mark        = 1 << OPTBIT_mark    ,
 	OPT_nofork      = 1 << OPTBIT_nofork  ,
@@ -225,6 +233,8 @@
 	OPT_circularlog = IF_FEATURE_IPC_SYSLOG(    (1 << OPTBIT_circularlog)) + 0,
 	OPT_dup         = IF_FEATURE_SYSLOGD_DUP(   (1 << OPTBIT_dup        )) + 0,
 	OPT_cfg         = IF_FEATURE_SYSLOGD_CFG(   (1 << OPTBIT_cfg        )) + 0,
+	OPT_kmsg        = IF_FEATURE_KMSG_SYSLOG(   (1 << OPTBIT_kmsg       )) + 0,
+
 };
 #define OPTION_STR "m:nO:l:S" \
 	IF_FEATURE_ROTATE_LOGFILE("s:" ) \
@@ -233,7 +243,8 @@
 	IF_FEATURE_REMOTE_LOG(    "L"  ) \
 	IF_FEATURE_IPC_SYSLOG(    "C::") \
 	IF_FEATURE_SYSLOGD_DUP(   "D"  ) \
-	IF_FEATURE_SYSLOGD_CFG(   "f:"  )
+	IF_FEATURE_SYSLOGD_CFG(   "f:" ) \
+	IF_FEATURE_KMSG_SYSLOG(   "K"  )
 #define OPTION_DECL *opt_m, *opt_l \
 	IF_FEATURE_ROTATE_LOGFILE(,*opt_s) \
 	IF_FEATURE_ROTATE_LOGFILE(,*opt_b) \
@@ -523,6 +534,44 @@
 void log_to_shmem(const char *msg);
 #endif /* FEATURE_IPC_SYSLOG */
 
+#if ENABLE_FEATURE_KMSG_SYSLOG
+static void kmsg_init(void)
+{
+	G.kmsgfd = xopen("/dev/kmsg", O_WRONLY);
+
+	/*
+	 * kernel < 3.5 expects single char printk KERN_* priority prefix,
+	 * from 3.5 onwards the full syslog facility/priority format is supported
+	 */
+	if (get_linux_version_code() < KERNEL_VERSION(3,5,0))
+		G.primask = LOG_PRIMASK;
+	else
+		G.primask = -1;
+}
+
+static void kmsg_cleanup(void)
+{
+	if (ENABLE_FEATURE_CLEAN_UP)
+		close(G.kmsgfd);
+}
+
+/* Write message to /dev/kmsg */
+static void log_to_kmsg(int pri, const char *msg)
+{
+	/*
+	 * kernel < 3.5 expects single char printk KERN_* priority prefix,
+	 * from 3.5 onwards the full syslog facility/priority format is supported
+	 */
+	pri &= G.primask;
+
+	write(G.kmsgfd, G.printbuf, sprintf(G.printbuf, "<%d>%s\n", pri, msg));
+}
+#else
+void kmsg_init(void);
+void kmsg_cleanup(void);
+void log_to_kmsg(int pri, const char *msg);
+#endif /* FEATURE_KMSG_SYSLOG */
+
 /* Print a message to the log file. */
 static void log_locally(time_t now, char *msg, logFile_t *log_file)
 {
@@ -657,6 +706,11 @@
 	}
 	timestamp[15] = '\0';
 
+	if (ENABLE_FEATURE_KMSG_SYSLOG && (option_mask32 & OPT_kmsg)) {
+		log_to_kmsg(pri, msg);
+		return;
+	}
+
 	if (option_mask32 & OPT_small)
 		sprintf(G.printbuf, "%s %s\n", timestamp, msg);
 	else {
@@ -831,6 +885,9 @@
 		ipcsyslog_init();
 	}
 
+	if (ENABLE_FEATURE_KMSG_SYSLOG && (option_mask32 & OPT_kmsg))
+		kmsg_init();
+
 	timestamp_and_log_internal("syslogd started: BusyBox v" BB_VER);
 
 	while (!bb_got_signal) {
@@ -919,6 +976,8 @@
 	remove_pidfile(CONFIG_PID_FILE_PATH "/syslogd.pid");
 	if (ENABLE_FEATURE_IPC_SYSLOG)
 		ipcsyslog_cleanup();
+	if (ENABLE_FEATURE_KMSG_SYSLOG && (option_mask32 & OPT_kmsg))
+		kmsg_cleanup();
 	kill_myself_with_sig(bb_got_signal);
 #undef recvbuf
 }