klogd: make it work on non-linux systems

The klogctl() interface allows changing the console loglevel, but is
Linux-specific. The more portable method of reading from _PATH_KLOG is
added as an alternative.

Adapted from the Debian kFreeBSD patch at:
http://svn.debian.org/viewsvn/d-i/people/slackydeb/kfreebsd/busybox/1.14/debian/klogd.diff

Signed-off-by: Jeremie Koenig <jk@jk.fr.eu.org>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Jeremie Koenig 2010-08-01 03:01:44 +02:00 committed by Denys Vlasenko
parent 17662801ec
commit 63c2e7ecc0
2 changed files with 126 additions and 19 deletions

View File

@ -109,7 +109,6 @@ config FEATURE_LOGREAD_REDUCED_LOCKING
config KLOGD
bool "klogd"
default y
depends on PLATFORM_LINUX
help
klogd is a utility which intercepts and logs all
messages from the Linux kernel and sends the messages
@ -117,6 +116,22 @@ config KLOGD
you wish to record the messages produced by the kernel,
you should enable this option.
config FEATURE_KLOGD_KLOGCTL
bool "Use the klogctl() interface"
default y
depends on KLOGD && PLATFORM_LINUX
help
The klogd applet supports two interfaces for reading
kernel messages. Linux provides the klogctl() interface
which allows reading messages from the kernel ring buffer
independently from the file system.
If you answer 'N' here, klogd will use the more portable
approach of reading them from /proc or a device node.
However, this method requires the file to be available.
If in doubt, say 'Y'.
config LOGGER
bool "logger"
default y

View File

@ -19,18 +19,93 @@
#include "libbb.h"
#include <syslog.h>
/* The Linux-specific klogctl(3) interface does not rely on the filesystem and
* allows us to change the console loglevel. Alternatively, we read the
* messages from _PATH_KLOG. */
#if ENABLE_FEATURE_KLOGD_KLOGCTL
# include <sys/klog.h>
static void klogd_signal(int sig)
static void klogd_open(void)
{
/* "Open the log. Currently a NOP" */
klogctl(1, NULL, 0);
}
static void klogd_setloglevel(int lvl)
{
/* "printk() prints a message on the console only if it has a loglevel
* less than console_loglevel". Here we set console_loglevel = lvl. */
klogctl(8, NULL, lvl);
}
static int klogd_read(char *bufp, int len)
{
return klogctl(2, bufp, len);
}
# define READ_ERROR "klogctl(2) error"
static void klogd_close(void)
{
/* FYI: cmd 7 is equivalent to setting console_loglevel to 7
* via klogctl(8, NULL, 7). */
klogctl(7, NULL, 0); /* "7 -- Enable printk's to console" */
klogctl(0, NULL, 0); /* "0 -- Close the log. Currently a NOP" */
syslog(LOG_NOTICE, "klogd: exiting");
kill_myself_with_sig(sig);
}
#else
# include <paths.h>
# ifndef _PATH_KLOG
# ifdef __GNU__
# define _PATH_KLOG "/dev/klog"
# else
# error "your system's _PATH_KLOG is unknown"
# endif
# endif
# define PATH_PRINTK "/proc/sys/kernel/printk"
enum { klogfd = 3 };
static void klogd_open(void)
{
int fd = xopen(_PATH_KLOG, O_RDONLY);
xmove_fd(fd, klogfd);
}
static void klogd_setloglevel(int lvl)
{
FILE *fp = fopen_or_warn(PATH_PRINTK, "w");
if (fp) {
/* This changes only first value:
* "messages with a higher priority than this
* [that is, with numerically lower value]
* will be printed to the console".
* The other three values in this pseudo-file aren't changed.
*/
fprintf(fp, "%u\n", lvl);
fclose(fp);
}
}
static int klogd_read(char *bufp, int len)
{
return read(klogfd, bufp, len);
}
# define READ_ERROR "read error"
static void klogd_close(void)
{
klogd_setloglevel(7);
if (ENABLE_FEATURE_CLEAN_UP)
close(klogfd);
}
#endif
#define log_buffer bb_common_bufsiz1
enum {
KLOGD_LOGBUF_SIZE = sizeof(log_buffer),
@ -38,6 +113,19 @@ enum {
OPT_FOREGROUND = (1 << 1),
};
/* TODO: glibc openlog(LOG_KERN) reverts to LOG_USER instead,
* because that's how they interpret word "default"
* in the openlog() manpage:
* LOG_USER (default)
* generic user-level messages
* and the fact that LOG_KERN is a constant 0.
* glibc interprets it as "0 in openlog() call means 'use default'".
* I think it means "if openlog wasn't called before syslog() is called,
* use default".
* Convincing glibc maintainers otherwise is, as usual, nearly impossible.
* Should we open-code syslog() here to use correct facility?
*/
int klogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int klogd_main(int argc UNUSED_PARAM, char **argv)
{
@ -55,34 +143,34 @@ int klogd_main(int argc UNUSED_PARAM, char **argv)
bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv);
}
logmode = LOGMODE_SYSLOG;
/* klogd_open() before openlog(), since it might use fixed fd 3,
* and openlog() also may use the same fd 3 if we swap them:
*/
klogd_open();
openlog("kernel", 0, LOG_KERN);
bb_signals(BB_FATAL_SIGS, klogd_signal);
signal(SIGHUP, SIG_IGN);
/* "Open the log. Currently a NOP" */
klogctl(1, NULL, 0);
/* "printk() prints a message on the console only if it has a loglevel
* less than console_loglevel". Here we set console_loglevel = i. */
if (i)
klogctl(8, NULL, i);
klogd_setloglevel(i);
bb_signals(BB_FATAL_SIGS, record_signo);
signal(SIGHUP, SIG_IGN);
syslog(LOG_NOTICE, "klogd started: %s", bb_banner);
while (1) {
while (!bb_got_signal) {
int n;
int priority;
char *start;
/* "2 -- Read from the log." */
start = log_buffer + used;
n = klogctl(2, start, KLOGD_LOGBUF_SIZE-1 - used);
n = klogd_read(start, KLOGD_LOGBUF_SIZE-1 - used);
if (n < 0) {
if (errno == EINTR)
continue;
syslog(LOG_ERR, "klogd: error %d in klogctl(2): %m",
errno);
bb_perror_msg(READ_ERROR);
break;
}
start[n] = '\0';
@ -131,5 +219,9 @@ int klogd_main(int argc UNUSED_PARAM, char **argv)
}
}
klogd_close();
syslog(LOG_NOTICE, "klogd: exiting");
if (bb_got_signal)
kill_myself_with_sig(bb_got_signal);
return EXIT_FAILURE;
}