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.7 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: "Log kernel messages to syslog\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 /* "2 -- Read from the log." */ 89 return klogctl(2, bufp, len); 90} 91# define READ_ERROR "klogctl(2) error" 92 93static void klogd_close(void) 94{ 95 /* FYI: cmd 7 is equivalent to setting console_loglevel to 7 96 * via klogctl(8, NULL, 7). */ 97 klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */ 98 klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */ 99} 100 101#else 102 103# ifndef _PATH_KLOG 104# ifdef __GNU__ 105# define _PATH_KLOG "/dev/klog" 106# else 107# error "your system's _PATH_KLOG is unknown" 108# endif 109# endif 110# define PATH_PRINTK "/proc/sys/kernel/printk" 111 112enum { klogfd = 3 }; 113 114static void klogd_open(void) 115{ 116 int fd = xopen(_PATH_KLOG, O_RDONLY); 117 xmove_fd(fd, klogfd); 118} 119 120static void klogd_setloglevel(int lvl) 121{ 122 FILE *fp = fopen_or_warn(PATH_PRINTK, "w"); 123 if (fp) { 124 /* This changes only first value: 125 * "messages with a higher priority than this 126 * [that is, with numerically lower value] 127 * will be printed to the console". 128 * The other three values in this pseudo-file aren't changed. 129 */ 130 fprintf(fp, "%u\n", lvl); 131 fclose(fp); 132 } 133} 134 135static int klogd_read(char *bufp, int len) 136{ 137 return read(klogfd, bufp, len); 138} 139# define READ_ERROR "read error" 140 141static void klogd_close(void) 142{ 143 klogd_setloglevel(7); 144 if (ENABLE_FEATURE_CLEAN_UP) 145 close(klogfd); 146} 147 148#endif 149 150#define log_buffer bb_common_bufsiz1 151enum { 152 KLOGD_LOGBUF_SIZE = COMMON_BUFSIZE, 153 OPT_LEVEL = (1 << 0), 154 OPT_FOREGROUND = (1 << 1), 155}; 156 157/* TODO: glibc openlog(LOG_KERN) reverts to LOG_USER instead, 158 * because that's how they interpret word "default" 159 * in the openlog() manpage: 160 * LOG_USER (default) 161 * generic user-level messages 162 * and the fact that LOG_KERN is a constant 0. 163 * glibc interprets it as "0 in openlog() call means 'use default'". 164 * I think it means "if openlog wasn't called before syslog() is called, 165 * use default". 166 * Convincing glibc maintainers otherwise is, as usual, nearly impossible. 167 * Should we open-code syslog() here to use correct facility? 168 */ 169 170int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 171int klogd_main(int argc UNUSED_PARAM, char **argv) 172{ 173 int i = 0; 174 char *opt_c; 175 int opt; 176 int used; 177 178 setup_common_bufsiz(); 179 180 opt = getopt32(argv, "c:n", &opt_c); 181 if (opt & OPT_LEVEL) { 182 /* Valid levels are between 1 and 8 */ 183 i = xatou_range(opt_c, 1, 8); 184 } 185 if (!(opt & OPT_FOREGROUND)) { 186 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); 187 } 188 189 logmode = LOGMODE_SYSLOG; 190 191 /* klogd_open() before openlog(), since it might use fixed fd 3, 192 * and openlog() also may use the same fd 3 if we swap them: 193 */ 194 klogd_open(); 195 openlog("kernel", 0, LOG_KERN); 196 /* 197 * glibc problem: for some reason, glibc changes LOG_KERN to LOG_USER 198 * above. The logic behind this is that standard 199 * http://pubs.opengroup.org/onlinepubs/9699919799/functions/syslog.html 200 * says the following about openlog and syslog: 201 * "LOG_USER 202 * Messages generated by arbitrary processes. 203 * This is the default facility identifier if none is specified." 204 * 205 * I believe glibc misinterpreted this text as "if openlog's 206 * third parameter is 0 (=LOG_KERN), treat it as LOG_USER". 207 * Whereas it was meant to say "if *syslog* is called with facility 208 * 0 in its 1st parameter without prior call to openlog, then perform 209 * implicit openlog(LOG_USER)". 210 * 211 * As a result of this, eh, feature, standard klogd was forced 212 * to open-code its own openlog and syslog implementation (!). 213 * 214 * Note that prohibiting openlog(LOG_KERN) on libc level does not 215 * add any security: any process can open a socket to "/dev/log" 216 * and write a string "<0>Voila, a LOG_KERN + LOG_EMERG message" 217 * 218 * Google code search tells me there is no widespread use of 219 * openlog("foo", 0, 0), thus fixing glibc won't break userspace. 220 * 221 * The bug against glibc was filed: 222 * bugzilla.redhat.com/show_bug.cgi?id=547000 223 */ 224 225 if (i) 226 klogd_setloglevel(i); 227 228 signal(SIGHUP, SIG_IGN); 229 /* We want klogd_read to not be restarted, thus _norestart: */ 230 bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo); 231 232 syslog(LOG_NOTICE, "klogd started: %s", bb_banner); 233 234 write_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 235 236 used = 0; 237 while (!bb_got_signal) { 238 int n; 239 int priority; 240 char *start; 241 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 char *end; 280 priority = strtoul(start, &end, 10); 281 if (*end == '>') 282 end++; 283 start = end; 284 } 285 } 286 /* Log (only non-empty lines) */ 287 if (*start) 288 syslog(priority, "%s", start); 289 290 if (!newline) 291 break; 292 start = newline; 293 } 294 } 295 296 klogd_close(); 297 syslog(LOG_NOTICE, "klogd: exiting"); 298 remove_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 299 if (bb_got_signal) 300 kill_myself_with_sig(bb_got_signal); 301 return EXIT_FAILURE; 302} 303