1/* vi: set sw=4 ts=4: */ 2/* 3 * Mini klogd implementation for busybox 4 * 5 * Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>. 6 * Changes: Made this a standalone busybox module which uses standalone 7 * syslog() client interface. 8 * 9 * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> 10 * 11 * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org> 12 * 13 * "circular buffer" Copyright (C) 2000 by Gennady Feldman <gfeldman@gena01.com> 14 * 15 * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 16 * 17 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 18 */ 19//config:config KLOGD 20//config: bool "klogd (5.5 kb)" 21//config: default y 22//config: help 23//config: klogd is a utility which intercepts and logs all 24//config: messages from the Linux kernel and sends the messages 25//config: out to the 'syslogd' utility so they can be logged. If 26//config: you wish to record the messages produced by the kernel, 27//config: you should enable this option. 28//config: 29//config:comment "klogd should not be used together with syslog to kernel printk buffer" 30//config: depends on KLOGD && FEATURE_KMSG_SYSLOG 31//config: 32//config:config FEATURE_KLOGD_KLOGCTL 33//config: bool "Use the klogctl() interface" 34//config: default y 35//config: depends on KLOGD 36//config: select PLATFORM_LINUX 37//config: help 38//config: The klogd applet supports two interfaces for reading 39//config: kernel messages. Linux provides the klogctl() interface 40//config: which allows reading messages from the kernel ring buffer 41//config: independently from the file system. 42//config: 43//config: If you answer 'N' here, klogd will use the more portable 44//config: approach of reading them from /proc or a device node. 45//config: However, this method requires the file to be available. 46//config: 47//config: If in doubt, say 'Y'. 48 49//applet:IF_KLOGD(APPLET(klogd, BB_DIR_SBIN, BB_SUID_DROP)) 50 51//kbuild:lib-$(CONFIG_KLOGD) += klogd.o 52 53//usage:#define klogd_trivial_usage 54//usage: "[-c N] [-n]" 55//usage:#define klogd_full_usage "\n\n" 56//usage: "Kernel logger\n" 57//usage: "\n -c N Print to console messages more urgent than prio N (1-8)" 58//usage: "\n -n Run in foreground" 59 60#include "libbb.h" 61#include "common_bufsiz.h" 62#include <syslog.h> 63 64 65/* The Linux-specific klogctl(3) interface does not rely on the filesystem and 66 * allows us to change the console loglevel. Alternatively, we read the 67 * messages from _PATH_KLOG. */ 68 69#if ENABLE_FEATURE_KLOGD_KLOGCTL 70 71# include <sys/klog.h> 72 73static void klogd_open(void) 74{ 75 /* "Open the log. Currently a NOP" */ 76 klogctl(1, NULL, 0); 77} 78 79static void klogd_setloglevel(int lvl) 80{ 81 /* "printk() prints a message on the console only if it has a loglevel 82 * less than console_loglevel". Here we set console_loglevel = lvl. */ 83 klogctl(8, NULL, lvl); 84} 85 86static int klogd_read(char *bufp, int len) 87{ 88 return klogctl(2, bufp, len); 89} 90# define READ_ERROR "klogctl(2) error" 91 92static void klogd_close(void) 93{ 94 /* FYI: cmd 7 is equivalent to setting console_loglevel to 7 95 * via klogctl(8, NULL, 7). */ 96 klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */ 97 klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */ 98} 99 100#else 101 102# ifndef _PATH_KLOG 103# ifdef __GNU__ 104# define _PATH_KLOG "/dev/klog" 105# else 106# error "your system's _PATH_KLOG is unknown" 107# endif 108# endif 109# define PATH_PRINTK "/proc/sys/kernel/printk" 110 111enum { klogfd = 3 }; 112 113static void klogd_open(void) 114{ 115 int fd = xopen(_PATH_KLOG, O_RDONLY); 116 xmove_fd(fd, klogfd); 117} 118 119static void klogd_setloglevel(int lvl) 120{ 121 FILE *fp = fopen_or_warn(PATH_PRINTK, "w"); 122 if (fp) { 123 /* This changes only first value: 124 * "messages with a higher priority than this 125 * [that is, with numerically lower value] 126 * will be printed to the console". 127 * The other three values in this pseudo-file aren't changed. 128 */ 129 fprintf(fp, "%u\n", lvl); 130 fclose(fp); 131 } 132} 133 134static int klogd_read(char *bufp, int len) 135{ 136 return read(klogfd, bufp, len); 137} 138# define READ_ERROR "read error" 139 140static void klogd_close(void) 141{ 142 klogd_setloglevel(7); 143 if (ENABLE_FEATURE_CLEAN_UP) 144 close(klogfd); 145} 146 147#endif 148 149#define log_buffer bb_common_bufsiz1 150enum { 151 KLOGD_LOGBUF_SIZE = COMMON_BUFSIZE, 152 OPT_LEVEL = (1 << 0), 153 OPT_FOREGROUND = (1 << 1), 154}; 155 156/* TODO: glibc openlog(LOG_KERN) reverts to LOG_USER instead, 157 * because that's how they interpret word "default" 158 * in the openlog() manpage: 159 * LOG_USER (default) 160 * generic user-level messages 161 * and the fact that LOG_KERN is a constant 0. 162 * glibc interprets it as "0 in openlog() call means 'use default'". 163 * I think it means "if openlog wasn't called before syslog() is called, 164 * use default". 165 * Convincing glibc maintainers otherwise is, as usual, nearly impossible. 166 * Should we open-code syslog() here to use correct facility? 167 */ 168 169int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 170int klogd_main(int argc UNUSED_PARAM, char **argv) 171{ 172 int i = 0; 173 char *opt_c; 174 int opt; 175 int used; 176 177 setup_common_bufsiz(); 178 179 opt = getopt32(argv, "c:n", &opt_c); 180 if (opt & OPT_LEVEL) { 181 /* Valid levels are between 1 and 8 */ 182 i = xatou_range(opt_c, 1, 8); 183 } 184 if (!(opt & OPT_FOREGROUND)) { 185 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); 186 } 187 188 logmode = LOGMODE_SYSLOG; 189 190 /* klogd_open() before openlog(), since it might use fixed fd 3, 191 * and openlog() also may use the same fd 3 if we swap them: 192 */ 193 klogd_open(); 194 openlog("kernel", 0, LOG_KERN); 195 /* 196 * glibc problem: for some reason, glibc changes LOG_KERN to LOG_USER 197 * above. The logic behind this is that standard 198 * http://pubs.opengroup.org/onlinepubs/9699919799/functions/syslog.html 199 * says the following about openlog and syslog: 200 * "LOG_USER 201 * Messages generated by arbitrary processes. 202 * This is the default facility identifier if none is specified." 203 * 204 * I believe glibc misinterpreted this text as "if openlog's 205 * third parameter is 0 (=LOG_KERN), treat it as LOG_USER". 206 * Whereas it was meant to say "if *syslog* is called with facility 207 * 0 in its 1st parameter without prior call to openlog, then perform 208 * implicit openlog(LOG_USER)". 209 * 210 * As a result of this, eh, feature, standard klogd was forced 211 * to open-code its own openlog and syslog implementation (!). 212 * 213 * Note that prohibiting openlog(LOG_KERN) on libc level does not 214 * add any security: any process can open a socket to "/dev/log" 215 * and write a string "<0>Voila, a LOG_KERN + LOG_EMERG message" 216 * 217 * Google code search tells me there is no widespread use of 218 * openlog("foo", 0, 0), thus fixing glibc won't break userspace. 219 * 220 * The bug against glibc was filed: 221 * bugzilla.redhat.com/show_bug.cgi?id=547000 222 */ 223 224 if (i) 225 klogd_setloglevel(i); 226 227 signal(SIGHUP, SIG_IGN); 228 /* We want klogd_read to not be restarted, thus _norestart: */ 229 bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo); 230 231 syslog(LOG_NOTICE, "klogd started: %s", bb_banner); 232 233 write_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 234 235 used = 0; 236 while (!bb_got_signal) { 237 int n; 238 int priority; 239 char *start; 240 241 /* "2 -- Read from the log." */ 242 start = log_buffer + used; 243 n = klogd_read(start, KLOGD_LOGBUF_SIZE-1 - used); 244 if (n < 0) { 245 if (errno == EINTR) 246 continue; 247 bb_perror_msg(READ_ERROR); 248 break; 249 } 250 start[n] = '\0'; 251 252 /* Process each newline-terminated line in the buffer */ 253 start = log_buffer; 254 while (1) { 255 char *newline = strchrnul(start, '\n'); 256 257 if (*newline == '\0') { 258 /* This line is incomplete */ 259 260 /* move it to the front of the buffer */ 261 overlapping_strcpy(log_buffer, start); 262 used = newline - start; 263 if (used < KLOGD_LOGBUF_SIZE-1) { 264 /* buffer isn't full */ 265 break; 266 } 267 /* buffer is full, log it anyway */ 268 used = 0; 269 newline = NULL; 270 } else { 271 *newline++ = '\0'; 272 } 273 274 /* Extract the priority */ 275 priority = LOG_INFO; 276 if (*start == '<') { 277 start++; 278 if (*start) 279 priority = strtoul(start, &start, 10); 280 if (*start == '>') 281 start++; 282 } 283 /* Log (only non-empty lines) */ 284 if (*start) 285 syslog(priority, "%s", start); 286 287 if (!newline) 288 break; 289 start = newline; 290 } 291 } 292 293 klogd_close(); 294 syslog(LOG_NOTICE, "klogd: exiting"); 295 remove_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 296 if (bb_got_signal) 297 kill_myself_with_sig(bb_got_signal); 298 return EXIT_FAILURE; 299} 300