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 20//usage:#define klogd_trivial_usage 21//usage: "[-c N] [-n]" 22//usage:#define klogd_full_usage "\n\n" 23//usage: "Kernel logger\n" 24//usage: "\n -c N Print to console messages more urgent than prio N (1-8)" 25//usage: "\n -n Run in foreground" 26 27#include "libbb.h" 28#include <syslog.h> 29 30 31/* The Linux-specific klogctl(3) interface does not rely on the filesystem and 32 * allows us to change the console loglevel. Alternatively, we read the 33 * messages from _PATH_KLOG. */ 34 35#if ENABLE_FEATURE_KLOGD_KLOGCTL 36 37# include <sys/klog.h> 38 39static void klogd_open(void) 40{ 41 /* "Open the log. Currently a NOP" */ 42 klogctl(1, NULL, 0); 43} 44 45static void klogd_setloglevel(int lvl) 46{ 47 /* "printk() prints a message on the console only if it has a loglevel 48 * less than console_loglevel". Here we set console_loglevel = lvl. */ 49 klogctl(8, NULL, lvl); 50} 51 52static int klogd_read(char *bufp, int len) 53{ 54 return klogctl(2, bufp, len); 55} 56# define READ_ERROR "klogctl(2) error" 57 58static void klogd_close(void) 59{ 60 /* FYI: cmd 7 is equivalent to setting console_loglevel to 7 61 * via klogctl(8, NULL, 7). */ 62 klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */ 63 klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */ 64} 65 66#else 67 68# include <paths.h> 69# ifndef _PATH_KLOG 70# ifdef __GNU__ 71# define _PATH_KLOG "/dev/klog" 72# else 73# error "your system's _PATH_KLOG is unknown" 74# endif 75# endif 76# define PATH_PRINTK "/proc/sys/kernel/printk" 77 78enum { klogfd = 3 }; 79 80static void klogd_open(void) 81{ 82 int fd = xopen(_PATH_KLOG, O_RDONLY); 83 xmove_fd(fd, klogfd); 84} 85 86static void klogd_setloglevel(int lvl) 87{ 88 FILE *fp = fopen_or_warn(PATH_PRINTK, "w"); 89 if (fp) { 90 /* This changes only first value: 91 * "messages with a higher priority than this 92 * [that is, with numerically lower value] 93 * will be printed to the console". 94 * The other three values in this pseudo-file aren't changed. 95 */ 96 fprintf(fp, "%u\n", lvl); 97 fclose(fp); 98 } 99} 100 101static int klogd_read(char *bufp, int len) 102{ 103 return read(klogfd, bufp, len); 104} 105# define READ_ERROR "read error" 106 107static void klogd_close(void) 108{ 109 klogd_setloglevel(7); 110 if (ENABLE_FEATURE_CLEAN_UP) 111 close(klogfd); 112} 113 114#endif 115 116#define log_buffer bb_common_bufsiz1 117enum { 118 KLOGD_LOGBUF_SIZE = sizeof(log_buffer), 119 OPT_LEVEL = (1 << 0), 120 OPT_FOREGROUND = (1 << 1), 121}; 122 123/* TODO: glibc openlog(LOG_KERN) reverts to LOG_USER instead, 124 * because that's how they interpret word "default" 125 * in the openlog() manpage: 126 * LOG_USER (default) 127 * generic user-level messages 128 * and the fact that LOG_KERN is a constant 0. 129 * glibc interprets it as "0 in openlog() call means 'use default'". 130 * I think it means "if openlog wasn't called before syslog() is called, 131 * use default". 132 * Convincing glibc maintainers otherwise is, as usual, nearly impossible. 133 * Should we open-code syslog() here to use correct facility? 134 */ 135 136int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 137int klogd_main(int argc UNUSED_PARAM, char **argv) 138{ 139 int i = 0; 140 char *opt_c; 141 int opt; 142 int used; 143 144 opt = getopt32(argv, "c:n", &opt_c); 145 if (opt & OPT_LEVEL) { 146 /* Valid levels are between 1 and 8 */ 147 i = xatou_range(opt_c, 1, 8); 148 } 149 if (!(opt & OPT_FOREGROUND)) { 150 bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); 151 } 152 153 logmode = LOGMODE_SYSLOG; 154 155 /* klogd_open() before openlog(), since it might use fixed fd 3, 156 * and openlog() also may use the same fd 3 if we swap them: 157 */ 158 klogd_open(); 159 openlog("kernel", 0, LOG_KERN); 160 /* 161 * glibc problem: for some reason, glibc changes LOG_KERN to LOG_USER 162 * above. The logic behind this is that standard 163 * http://pubs.opengroup.org/onlinepubs/9699919799/functions/syslog.html 164 * says the following about openlog and syslog: 165 * "LOG_USER 166 * Messages generated by arbitrary processes. 167 * This is the default facility identifier if none is specified." 168 * 169 * I believe glibc misinterpreted this text as "if openlog's 170 * third parameter is 0 (=LOG_KERN), treat it as LOG_USER". 171 * Whereas it was meant to say "if *syslog* is called with facility 172 * 0 in its 1st parameter without prior call to openlog, then perform 173 * implicit openlog(LOG_USER)". 174 * 175 * As a result of this, eh, feature, standard klogd was forced 176 * to open-code its own openlog and syslog implementation (!). 177 * 178 * Note that prohibiting openlog(LOG_KERN) on libc level does not 179 * add any security: any process can open a socket to "/dev/log" 180 * and write a string "<0>Voila, a LOG_KERN + LOG_EMERG message" 181 * 182 * Google code search tells me there is no widespread use of 183 * openlog("foo", 0, 0), thus fixing glibc won't break userspace. 184 * 185 * The bug against glibc was filed: 186 * bugzilla.redhat.com/show_bug.cgi?id=547000 187 */ 188 189 if (i) 190 klogd_setloglevel(i); 191 192 signal(SIGHUP, SIG_IGN); 193 /* We want klogd_read to not be restarted, thus _norestart: */ 194 bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo); 195 196 syslog(LOG_NOTICE, "klogd started: %s", bb_banner); 197 198 write_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 199 200 used = 0; 201 while (!bb_got_signal) { 202 int n; 203 int priority; 204 char *start; 205 206 /* "2 -- Read from the log." */ 207 start = log_buffer + used; 208 n = klogd_read(start, KLOGD_LOGBUF_SIZE-1 - used); 209 if (n < 0) { 210 if (errno == EINTR) 211 continue; 212 bb_perror_msg(READ_ERROR); 213 break; 214 } 215 start[n] = '\0'; 216 217 /* Process each newline-terminated line in the buffer */ 218 start = log_buffer; 219 while (1) { 220 char *newline = strchrnul(start, '\n'); 221 222 if (*newline == '\0') { 223 /* This line is incomplete */ 224 225 /* move it to the front of the buffer */ 226 overlapping_strcpy(log_buffer, start); 227 used = newline - start; 228 if (used < KLOGD_LOGBUF_SIZE-1) { 229 /* buffer isn't full */ 230 break; 231 } 232 /* buffer is full, log it anyway */ 233 used = 0; 234 newline = NULL; 235 } else { 236 *newline++ = '\0'; 237 } 238 239 /* Extract the priority */ 240 priority = LOG_INFO; 241 if (*start == '<') { 242 start++; 243 if (*start) 244 priority = strtoul(start, &start, 10); 245 if (*start == '>') 246 start++; 247 } 248 /* Log (only non-empty lines) */ 249 if (*start) 250 syslog(priority, "%s", start); 251 252 if (!newline) 253 break; 254 start = newline; 255 } 256 } 257 258 klogd_close(); 259 syslog(LOG_NOTICE, "klogd: exiting"); 260 remove_pidfile(CONFIG_PID_FILE_PATH "/klogd.pid"); 261 if (bb_got_signal) 262 kill_myself_with_sig(bb_got_signal); 263 return EXIT_FAILURE; 264} 265