1130 lines
30 KiB
C
1130 lines
30 KiB
C
/*
|
||
* klogd.c - main program for Linux kernel log daemon.
|
||
* Copyright (c) 1995 Dr. G.W. Wettstein <greg@wind.rmcc.com>
|
||
*
|
||
* This file is part of the sysklogd package, a kernel and system log daemon.
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 2 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this file; see the file COPYING. If not, write to the
|
||
* Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston,
|
||
* MA 02110-1301, USA.
|
||
*/
|
||
|
||
/*
|
||
* Steve Lord (lord@cray.com) 7th Nov 92
|
||
*
|
||
* Modified to check for kernel info by Dr. G.W. Wettstein 02/17/93.
|
||
*
|
||
* Fri Mar 12 16:53:56 CST 1993: Dr. Wettstein
|
||
* Modified LogLine to use a newline as the line separator in
|
||
* the kernel message buffer.
|
||
*
|
||
* Added debugging code to dump the contents of the kernel message
|
||
* buffer at the start of the LogLine function.
|
||
*
|
||
* Thu Jul 29 11:40:32 CDT 1993: Dr. Wettstein
|
||
* Added syscalls to turn off logging of kernel messages to the
|
||
* console when klogd becomes responsible for kernel messages.
|
||
*
|
||
* klogd now catches SIGTERM and SIGKILL signals. Receipt of these
|
||
* signals cases the clean_up function to be called which shuts down
|
||
* kernel logging and re-enables logging of messages to the console.
|
||
*
|
||
* Sat Dec 11 11:54:22 CST 1993: Dr. Wettstein
|
||
* Added fixes to allow compilation with no complaints with -Wall.
|
||
*
|
||
* When the daemon catches a fatal signal (SIGTERM, SIGKILL) a
|
||
* message is output to the logfile advising that the daemon is
|
||
* going to terminate.
|
||
*
|
||
* Thu Jan 6 11:54:10 CST 1994: Dr. Wettstein
|
||
* Major re-write/re-organization of the code.
|
||
*
|
||
* Klogd now assigns kernel messages to priority levels when output
|
||
* to the syslog facility is requested. The priority level is
|
||
* determined by decoding the prioritization sequence which is
|
||
* tagged onto the start of the kernel messages.
|
||
*
|
||
* Added the following program options: -f arg -c arg -s -o -d
|
||
*
|
||
* The -f switch can be used to specify that output should
|
||
* be written to the named file.
|
||
*
|
||
* The -c switch is used to specify the level of kernel
|
||
* messages which are to be directed to the console.
|
||
*
|
||
* The -s switch causes the program to use the syscall
|
||
* interface to the kernel message facility. This can be
|
||
* used to override the presence of the /proc filesystem.
|
||
*
|
||
* The -o switch causes the program to operate in 'one-shot'
|
||
* mode. A single call will be made to read the complete
|
||
* kernel buffer. The contents of the buffer will be
|
||
* output and the program will terminate.
|
||
*
|
||
* The -d switch causes 'debug' mode to be activated. This
|
||
* will cause the daemon to generate LOTS of output to stderr.
|
||
*
|
||
* The buffer decomposition function (LogLine) was re-written to
|
||
* squash a bug which was causing only partial kernel messages to
|
||
* be written to the syslog facility.
|
||
*
|
||
* The signal handling code was modified to properly differentiate
|
||
* between the STOP and TSTP signals.
|
||
*
|
||
* Added pid saving when the daemon detaches into the background. Thank
|
||
* you to Juha Virtanen (jiivee@hut.fi) for providing this patch.
|
||
*
|
||
* Mon Feb 6 07:31:29 CST 1995: Dr. Wettstein
|
||
* Significant re-organization of the signal handling code. The
|
||
* signal handlers now only set variables. Not earth shaking by any
|
||
* means but aesthetically pleasing to the code purists in the group.
|
||
*
|
||
* Patch to make things more compliant with the file system standards.
|
||
* Thanks to Chris Metcalf for prompting this helpful change.
|
||
*
|
||
* The routines responsible for reading the kernel log sources now
|
||
* initialize the buffers before reading. I think that this will
|
||
* solve problems with non-terminated kernel messages producing
|
||
* output of the form: new old old old
|
||
*
|
||
* This may also help influence the occassional reports of klogd
|
||
* failing under significant load. I think that the jury may still
|
||
* be out on this one though. My thanks to Joerg Ahrens for initially
|
||
* tipping me off to the source of this problem. Also thanks to
|
||
* Michael O'Reilly for tipping me off to the best fix for this problem.
|
||
* And last but not least Mark Lord for prompting me to try this as
|
||
* a means of attacking the stability problem.
|
||
*
|
||
* Specifying a - as the arguement to the -f switch will cause output
|
||
* to be directed to stdout rather than a filename of -. Thanks to
|
||
* Randy Appleton for a patch which prompted me to do this.
|
||
*
|
||
* Wed Feb 22 15:37:37 CST 1995: Dr. Wettstein
|
||
* Added version information to logging startup messages.
|
||
*
|
||
* Wed Jul 26 18:57:23 MET DST 1995: Martin Schulze
|
||
* Added an commandline argument "-n" to avoid forking. This obsoletes
|
||
* the compiler define NO_FORK. It's more useful to have this as an
|
||
* argument as there are many binary versions and one doesn't need to
|
||
* recompile the daemon.
|
||
*
|
||
* Thu Aug 10 19:01:08 MET DST 1995: Martin Schulze
|
||
* Added my pidfile.[ch] to it to perform a better handling with pidfiles.
|
||
* Now both, syslogd and klogd, can only be started once. They check the
|
||
* pidfile.
|
||
*
|
||
* Fri Nov 17 15:05:43 CST 1995: Dr. Wettstein
|
||
* Added support for kernel address translation. This required moving
|
||
* some definitions and includes to the new klogd.h file. Some small
|
||
* code cleanups and modifications.
|
||
*
|
||
* Mon Nov 20 10:03:39 MET 1995
|
||
* Added -v option to print the version and exit.
|
||
*
|
||
* Thu Jan 18 11:19:46 CST 1996: Dr. Wettstein
|
||
* Added suggested patches from beta-testers. These address two
|
||
* two problems. The first is segmentation faults which occur with
|
||
* the ELF libraries. This was caused by passing a null pointer to
|
||
* the strcmp function.
|
||
*
|
||
* Added a second patch to remove the pidfile as part of the
|
||
* termination cleanup sequence. This minimizes the potential for
|
||
* conflicting pidfiles causing immediate termination at boot time.
|
||
*
|
||
* Wed Aug 21 09:13:03 CDT 1996: Dr. Wettstein
|
||
* Added ability to reload static symbols and kernel module symbols
|
||
* under control of SIGUSR1 and SIGUSR2 signals.
|
||
*
|
||
* Added -p switch to select 'paranoid' behavior with respect to the
|
||
* loading of kernel module symbols.
|
||
*
|
||
* Informative line now printed whenever a state change occurs due
|
||
* to signal reception by the daemon.
|
||
*
|
||
* Added the -i and -I command line switches to signal the currently
|
||
* executing daemon.
|
||
*
|
||
* Tue Nov 19 10:15:36 PST 1996: Leland Olds <olds@eskimo.com>
|
||
* Corrected vulnerability to buffer overruns by rewriting LogLine
|
||
* routine. Obscenely long kernel messages will now be broken up
|
||
* into lines no longer than LOG_LINE_LENGTH.
|
||
*
|
||
* The last version of LogLine was vulnerable to buffer overruns:
|
||
* - Kernel messages longer than LOG_LINE_LENGTH caused a buffer
|
||
* overrun.
|
||
* - If a line was determined to be shorter than LOG_LINE_LENGTH,
|
||
* the routine "ExpandKadds" could cause the line grow by
|
||
* an unknown amount and overrun a buffer.
|
||
* I turned these routines into a little parsing state machine that
|
||
* should not have these problems.
|
||
*
|
||
* Sun Jun 15 16:23:29 MET DST 1997: Michael Alan Dorman
|
||
* Some more glibc patches made by <mdorman@debian.org>.
|
||
*
|
||
* Thu Aug 21 12:11:27 MET DST 1997: Martin Schulze <joey@infodrom.north.de>
|
||
* Fixed little mistake which prevented klogd from accepting a
|
||
* console log
|
||
*
|
||
* Fri Jan 9 00:39:52 CET 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* Changed the behaviour of klogd when receiving a terminate
|
||
* signal. Now the program terminates immediately instead of
|
||
* completing the receipt of a kernel message, i.e the read()
|
||
* call. The old behaveiour could result in klogd being
|
||
* recognized as being undead, because it'll only die after a
|
||
* message has been received.
|
||
*
|
||
* Fri Jan 9 11:03:48 CET 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* Corrected some code that caused klogd to dump core when
|
||
* receiving messages containing '%', some of them exist in
|
||
* 2.1.78. Thanks to Chu-yeon Park <kokids@doit.ajou.ac.kr> for
|
||
* informing me.
|
||
*
|
||
* Fri Jan 9 23:38:19 CET 1998: Florian La Roche <florian@knorke.saar.de>
|
||
* Added -x switch to omit EIP translation and System.map evaluation.
|
||
*
|
||
* Sun Jan 25 20:47:46 CET 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* As the bug covering the %'s introduced a problem with
|
||
* unevaluated priorities I've worked out a real fix that strips
|
||
* %'s to an even number which is harmless for printf.
|
||
*
|
||
* Sat Oct 10 20:01:48 CEST 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* Added support for TESTING define which will turn klogd into
|
||
* stdio-mode used for debugging.
|
||
*
|
||
* Mon Apr 13 18:18:45 CEST 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* Modified System.map read function to try all possible map
|
||
* files until a file with matching version is found. Added support for
|
||
* Debian release.
|
||
*
|
||
* Mon Oct 12 13:01:27 MET DST 1998: Martin Schulze <joey@infodrom.north.de>
|
||
* Used unsigned long and strtoul() to resolve kernel oops symbols.
|
||
*
|
||
* Sun Jan 3 18:38:03 CET 1999: Martin Schulze <joey@infodrom.north.de>
|
||
* Shortened LOG_LINE_LENGTH in order to get long lines splitted
|
||
* up earlier and syslogd has a better chance concatenating them
|
||
* together again.
|
||
*
|
||
* Sat Aug 21 12:27:02 CEST 1999: Martin Schulze <joey@infodrom.north.de>
|
||
* Skip newline when reading in messages.
|
||
*
|
||
* Tue Sep 12 22:14:33 CEST 2000: Martin Schulze <joey@infodrom.ffis.de>
|
||
* Don't feed a buffer directly to a printf-type routine, use
|
||
* "%s" as format string instead. Thanks to Jouko Pynn<6E>nen
|
||
* <jouko@solutions.fi> for pointing this out.
|
||
*
|
||
* Tue Sep 12 22:44:57 CEST 2000: Martin Schulze <joey@infodrom.ffis.de>
|
||
* Commandline option `-2': When symbols are expanded, print the
|
||
* line twice. Once with addresses converted to symbols, once with the
|
||
* raw text. Allows external programs such as ksymoops do their own
|
||
* processing on the original data. Thanks to Keith Owens
|
||
* <kaos@ocs.com.au> for the patch.
|
||
*
|
||
* Mon Sep 18 09:32:27 CEST 2000: Martin Schulze <joey@infodrom.ffis.de>
|
||
* Added patch to fix priority decoding after moving kernel
|
||
* messgages into "%s". Thanks to Solar Designer
|
||
* <solar@false.com> for the patch.
|
||
*
|
||
* Sun Mar 11 20:23:44 CET 2001: Martin Schulze <joey@infodrom.ffis.de>
|
||
* Stop LogLine() from being called with wrong argument when a
|
||
* former calculation failed already. Thanks to Thomas Roessler
|
||
* <roessler@does-not-exist.org> for providing a patch.
|
||
*
|
||
* Ignore zero bytes, no busy loop is entered anymore. Several
|
||
* people have submitted patches: Troels Walsted Hansen
|
||
* <troels@thule.no>, Wolfgang Oertl <Wolfgang.Oertl@uibk.ac.at>
|
||
* and Thomas Roessler.
|
||
*
|
||
* Thu Apr 29 15:24:07 2004: Solar Designer <solar@openwall.com>
|
||
* Prevent potential buffer overflow in reading messages from the
|
||
* kernel log rinbuffer.
|
||
*
|
||
* Sat May 26 16:33:18 2007: Martin Schulze <joey@infodrom.org>
|
||
* Improved daemonise routine to stabilise startup.
|
||
*
|
||
* Mon May 28 18:07:59 CEST 2007: Matthew Fischer <futhark@vzavenue.net>
|
||
* Remove special treatment of the percent sign.
|
||
*/
|
||
|
||
/* Includes. */
|
||
#include <errno.h>
|
||
#include <getopt.h>
|
||
#include <signal.h>
|
||
#include <unistd.h>
|
||
#ifdef SYSV
|
||
#include <fcntl.h>
|
||
#else
|
||
#include <sys/msgbuf.h>
|
||
#endif
|
||
#include "klogd.h"
|
||
#include "ksyms.h"
|
||
#include <paths.h>
|
||
#include <stdarg.h>
|
||
#include <stdlib.h>
|
||
#include <sys/stat.h>
|
||
#ifndef TESTING
|
||
#include "pidfile.h"
|
||
#endif
|
||
#include "config.h"
|
||
|
||
#define __LIBRARY__
|
||
#include <linux/unistd.h>
|
||
#include <sys/klog.h>
|
||
#define ksyslog klogctl
|
||
|
||
#define LOG_BUFFER_SIZE 4096
|
||
#define LOG_LINE_LENGTH 1000
|
||
|
||
#ifndef TESTING
|
||
#if defined(FSSTND)
|
||
static char *PidFile = _PATH_VARRUN "klogd.pid";
|
||
#else
|
||
static char *PidFile = "/etc/klogd.pid";
|
||
#endif
|
||
#endif
|
||
|
||
static int kmsg;
|
||
static int change_state = 0;
|
||
static int terminate = 0;
|
||
static int caught_TSTP = 0;
|
||
static int reload_symbols = 0;
|
||
static int console_log_level = -1;
|
||
|
||
static int use_syscall = 0;
|
||
static int one_shot = 0;
|
||
static int symbol_lookup = 1;
|
||
static int no_fork = 0; /* don't fork - don't run in daemon mode */
|
||
|
||
static char *symfile = NULL;
|
||
static char log_buffer[LOG_BUFFER_SIZE];
|
||
|
||
static FILE *output_file = NULL;
|
||
|
||
static enum LOGSRC { none, proc, kernel } logsrc;
|
||
|
||
int debugging = 0;
|
||
int symbols_twice = 0;
|
||
|
||
/* Function prototypes. */
|
||
extern int ksyslog(int type, char *buf, int len);
|
||
static void CloseLogSrc(void);
|
||
extern void restart(int sig);
|
||
extern void stop_logging(int sig);
|
||
extern void stop_daemon(int sig);
|
||
extern void reload_daemon(int sig);
|
||
static void Terminate(void);
|
||
static void SignalDaemon(int);
|
||
static void ReloadSymbols(void);
|
||
static void ChangeLogging(void);
|
||
static enum LOGSRC GetKernelLogSrc(void);
|
||
static void LogLine(char *ptr, int len);
|
||
static void LogKernelLine(void);
|
||
static void LogProcLine(void);
|
||
extern int main(int argc, char *argv[]);
|
||
|
||
static void CloseLogSrc(void)
|
||
{
|
||
/* Shutdown the log sources. */
|
||
switch (logsrc) {
|
||
case kernel:
|
||
ksyslog(0, 0, 0);
|
||
Syslog(LOG_INFO, "Kernel logging (ksyslog) stopped.");
|
||
break;
|
||
|
||
case proc:
|
||
close(kmsg);
|
||
Syslog(LOG_INFO, "Kernel logging (proc) stopped.");
|
||
break;
|
||
|
||
case none:
|
||
break;
|
||
}
|
||
|
||
if (output_file != NULL)
|
||
fflush(output_file);
|
||
}
|
||
|
||
/*
|
||
* Signal handler to terminate the parent process.
|
||
*/
|
||
#ifndef TESTING
|
||
void doexit(int signo)
|
||
{
|
||
exit(0);
|
||
}
|
||
#endif
|
||
|
||
void restart(int signo)
|
||
{
|
||
signal(SIGCONT, restart);
|
||
change_state = 1;
|
||
caught_TSTP = 0;
|
||
}
|
||
|
||
void stop_logging(int signo)
|
||
{
|
||
signal(SIGTSTP, stop_logging);
|
||
change_state = 1;
|
||
caught_TSTP = 1;
|
||
}
|
||
|
||
void stop_daemon(int signo)
|
||
{
|
||
Terminate();
|
||
}
|
||
|
||
void reload_daemon(int signo)
|
||
{
|
||
change_state = 1;
|
||
reload_symbols = 1;
|
||
|
||
if (signo == SIGUSR2) {
|
||
++reload_symbols;
|
||
signal(SIGUSR2, reload_daemon);
|
||
} else
|
||
signal(SIGUSR1, reload_daemon);
|
||
|
||
return;
|
||
}
|
||
|
||
static void Terminate(void)
|
||
{
|
||
CloseLogSrc();
|
||
Syslog(LOG_INFO, "Kernel log daemon terminating.");
|
||
sleep(1);
|
||
if (output_file != NULL)
|
||
fclose(output_file);
|
||
closelog();
|
||
#ifndef TESTING
|
||
(void)remove_pid(PidFile);
|
||
#endif
|
||
exit(1);
|
||
}
|
||
|
||
static void SignalDaemon(int signo)
|
||
{
|
||
#ifndef TESTING
|
||
int pid = check_pid(PidFile);
|
||
|
||
kill(pid, signo);
|
||
#else
|
||
kill(getpid(), signo);
|
||
#endif
|
||
}
|
||
|
||
static void ReloadSymbols(void)
|
||
{
|
||
if (symbol_lookup) {
|
||
if (reload_symbols > 1)
|
||
InitKsyms(symfile);
|
||
InitMsyms();
|
||
}
|
||
reload_symbols = change_state = 0;
|
||
}
|
||
|
||
static void ChangeLogging(void)
|
||
{
|
||
/* Terminate kernel logging. */
|
||
if (terminate == 1)
|
||
Terminate();
|
||
|
||
/* Indicate that something is happening. */
|
||
Syslog(LOG_INFO, "klogd v%s, ---------- state change ----------\n",
|
||
PACKAGE_VERSION);
|
||
|
||
/* Reload symbols. */
|
||
if (reload_symbols > 0) {
|
||
ReloadSymbols();
|
||
return;
|
||
}
|
||
|
||
/* Stop kernel logging. */
|
||
if (caught_TSTP == 1) {
|
||
CloseLogSrc();
|
||
logsrc = none;
|
||
change_state = 0;
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* The rest of this function is responsible for restarting
|
||
* kernel logging after it was stopped.
|
||
*
|
||
* In the following section we make a decision based on the
|
||
* kernel log state as to what is causing us to restart. Somewhat
|
||
* groady but it keeps us from creating another static variable.
|
||
*/
|
||
if (logsrc != none) {
|
||
Syslog(LOG_INFO, "Kernel logging re-started after SIGSTOP.");
|
||
change_state = 0;
|
||
return;
|
||
}
|
||
|
||
/* Restart logging. */
|
||
logsrc = GetKernelLogSrc();
|
||
change_state = 0;
|
||
}
|
||
|
||
static enum LOGSRC GetKernelLogSrc(void)
|
||
{
|
||
struct stat sb;
|
||
|
||
/* Set level of kernel console messaging.. */
|
||
if ((console_log_level != -1) && (ksyslog(8, NULL, console_log_level) < 0) &&
|
||
(errno == EINVAL)) {
|
||
/*
|
||
* An invalid arguement error probably indicates that
|
||
* a pre-0.14 kernel is being run. At this point we
|
||
* issue an error message and simply shut-off console
|
||
* logging completely.
|
||
*/
|
||
Syslog(LOG_WARNING, "Cannot set console log level - disabling "
|
||
"console output.");
|
||
}
|
||
|
||
/*
|
||
* First do a stat to determine whether or not the proc based
|
||
* file system is available to get kernel messages from.
|
||
*/
|
||
if (use_syscall ||
|
||
((stat(_PATH_KLOG, &sb) < 0) && (errno == ENOENT))) {
|
||
/* Initialize kernel logging. */
|
||
ksyslog(1, NULL, 0);
|
||
Syslog(LOG_INFO, "klogd v%s, log source = ksyslog "
|
||
"started.",
|
||
PACKAGE_VERSION);
|
||
return kernel;
|
||
}
|
||
|
||
#ifndef TESTING
|
||
if ((kmsg = open(_PATH_KLOG, O_RDONLY)) < 0) {
|
||
fprintf(stderr, "klogd: Cannot open proc file system, "
|
||
"%d - %s.\n",
|
||
errno, strerror(errno));
|
||
ksyslog(7, NULL, 0);
|
||
exit(1);
|
||
}
|
||
#else
|
||
kmsg = fileno(stdin);
|
||
#endif
|
||
|
||
Syslog(LOG_INFO, "klogd v%s, log source = %s started.",
|
||
VERSION, _PATH_KLOG);
|
||
return proc;
|
||
}
|
||
|
||
extern void Syslog(int priority, char *fmt, ...)
|
||
|
||
{
|
||
va_list ap;
|
||
char *argl;
|
||
|
||
if (debugging) {
|
||
fputs("Logging line:\n", stderr);
|
||
fprintf(stderr, "\tLine: %s\n", fmt);
|
||
fprintf(stderr, "\tPriority: %d\n", priority);
|
||
}
|
||
|
||
/* Handle output to a file. */
|
||
if (output_file != NULL) {
|
||
va_start(ap, fmt);
|
||
vfprintf(output_file, fmt, ap);
|
||
va_end(ap);
|
||
fputc('\n', output_file);
|
||
fflush(output_file);
|
||
if (!one_shot)
|
||
fsync(fileno(output_file));
|
||
return;
|
||
}
|
||
|
||
/* Output using syslog. */
|
||
if (!strcmp(fmt, "%s")) {
|
||
va_start(ap, fmt);
|
||
argl = va_arg(ap, char *);
|
||
if (argl[0] == '<' && argl[1] && argl[2] == '>') {
|
||
switch (argl[1]) {
|
||
case '0':
|
||
priority = LOG_EMERG;
|
||
break;
|
||
case '1':
|
||
priority = LOG_ALERT;
|
||
break;
|
||
case '2':
|
||
priority = LOG_CRIT;
|
||
break;
|
||
case '3':
|
||
priority = LOG_ERR;
|
||
break;
|
||
case '4':
|
||
priority = LOG_WARNING;
|
||
break;
|
||
case '5':
|
||
priority = LOG_NOTICE;
|
||
break;
|
||
case '6':
|
||
priority = LOG_INFO;
|
||
break;
|
||
case '7':
|
||
default:
|
||
priority = LOG_DEBUG;
|
||
}
|
||
argl += 3;
|
||
}
|
||
syslog(priority, fmt, argl);
|
||
va_end(ap);
|
||
#ifdef TESTING
|
||
putchar('\n');
|
||
#endif
|
||
return;
|
||
}
|
||
|
||
va_start(ap, fmt);
|
||
vsyslog(priority, fmt, ap);
|
||
va_end(ap);
|
||
#ifdef TESTING
|
||
printf("\n");
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
* Copy characters from ptr to line until a char in the delim
|
||
* string is encountered or until min( space, len ) chars have
|
||
* been copied.
|
||
*
|
||
* Returns the actual number of chars copied.
|
||
*/
|
||
static int copyin(char *line, int space,
|
||
const char *ptr, int len,
|
||
const char *delim)
|
||
{
|
||
int i;
|
||
int count;
|
||
|
||
count = len < space ? len : space;
|
||
|
||
for (i = 0; i < count && !strchr(delim, *ptr); i++)
|
||
*line++ = *ptr++;
|
||
|
||
return i;
|
||
}
|
||
|
||
/*
|
||
* Messages are separated by "\n". Messages longer than
|
||
* LOG_LINE_LENGTH are broken up.
|
||
*
|
||
* Kernel symbols show up in the input buffer as : "[<aaaaaa>]",
|
||
* where "aaaaaa" is the address. These are replaced with
|
||
* "[symbolname+offset/size]" in the output line - symbolname,
|
||
* offset, and size come from the kernel symbol table.
|
||
*
|
||
* If a kernel symbol happens to fall at the end of a message close
|
||
* in length to LOG_LINE_LENGTH, the symbol will not be expanded.
|
||
* (This should never happen, since the kernel should never generate
|
||
* messages that long.
|
||
*
|
||
* To preserve the original addresses, lines containing kernel symbols
|
||
* are output twice. Once with the symbols converted and again with the
|
||
* original text. Just in case somebody wants to run their own Oops
|
||
* analysis on the syslog, e.g. ksymoops.
|
||
*/
|
||
static void LogLine(char *ptr, int len)
|
||
{
|
||
enum parse_state_enum {
|
||
PARSING_TEXT,
|
||
PARSING_SYMSTART, /* at < */
|
||
PARSING_SYMBOL,
|
||
PARSING_SYMEND /* at ] */
|
||
};
|
||
|
||
static char line_buff[LOG_LINE_LENGTH];
|
||
|
||
static char * line = line_buff;
|
||
static enum parse_state_enum parse_state = PARSING_TEXT;
|
||
static int space = sizeof(line_buff) - 1;
|
||
|
||
static char *sym_start; /* points at the '<' of a symbol */
|
||
|
||
int delta = 0; /* number of chars copied */
|
||
int symbols_expanded = 0; /* 1 if symbols were expanded */
|
||
int skip_symbol_lookup = 0; /* skip symbol lookup on this pass */
|
||
char *save_ptr = ptr; /* save start of input line */
|
||
int save_len = len; /* save length at start of input line */
|
||
|
||
while (len > 0) {
|
||
if (space == 0) { /* line buffer is full */
|
||
/*
|
||
** Line too long. Start a new line.
|
||
*/
|
||
*line = 0; /* force null terminator */
|
||
|
||
if (debugging) {
|
||
fputs("Line buffer full:\n", stderr);
|
||
fprintf(stderr, "\tLine: %s\n", line);
|
||
}
|
||
|
||
Syslog(LOG_INFO, "%s", line_buff);
|
||
line = line_buff;
|
||
space = sizeof(line_buff) - 1;
|
||
parse_state = PARSING_TEXT;
|
||
symbols_expanded = 0;
|
||
skip_symbol_lookup = 0;
|
||
save_ptr = ptr;
|
||
save_len = len;
|
||
}
|
||
|
||
switch (parse_state) {
|
||
case PARSING_TEXT:
|
||
delta = copyin(line, space, ptr, len, "\n[");
|
||
line += delta;
|
||
ptr += delta;
|
||
space -= delta;
|
||
len -= delta;
|
||
|
||
if (space == 0 || len == 0)
|
||
break; /* full line_buff or end of input buffer */
|
||
|
||
if (*ptr == '\0') { /* zero byte */
|
||
ptr++; /* skip zero byte */
|
||
space -= 1;
|
||
len -= 1;
|
||
|
||
break;
|
||
}
|
||
|
||
if (*ptr == '\n') { /* newline */
|
||
ptr++; /* skip newline */
|
||
space -= 1;
|
||
len -= 1;
|
||
|
||
*line = 0; /* force null terminator */
|
||
Syslog(LOG_INFO, "%s", line_buff);
|
||
line = line_buff;
|
||
space = sizeof(line_buff) - 1;
|
||
if (symbols_twice) {
|
||
if (symbols_expanded) {
|
||
/* reprint this line without symbol lookup */
|
||
symbols_expanded = 0;
|
||
skip_symbol_lookup = 1;
|
||
ptr = save_ptr;
|
||
len = save_len;
|
||
} else {
|
||
skip_symbol_lookup = 0;
|
||
save_ptr = ptr;
|
||
save_len = len;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
if (*ptr == '[') { /* possible kernel symbol */
|
||
*line++ = *ptr++;
|
||
space -= 1;
|
||
len -= 1;
|
||
if (!skip_symbol_lookup)
|
||
parse_state = PARSING_SYMSTART; /* at < */
|
||
break;
|
||
}
|
||
break;
|
||
|
||
case PARSING_SYMSTART:
|
||
if (*ptr != '<') {
|
||
parse_state = PARSING_TEXT; /* not a symbol */
|
||
break;
|
||
}
|
||
|
||
/*
|
||
** Save this character for now. If this turns out to
|
||
** be a valid symbol, this char will be replaced later.
|
||
** If not, we'll just leave it there.
|
||
*/
|
||
sym_start = line; /* this will point at the '<' */
|
||
|
||
*line++ = *ptr++;
|
||
space -= 1;
|
||
len -= 1;
|
||
parse_state = PARSING_SYMBOL; /* symbol... */
|
||
break;
|
||
|
||
case PARSING_SYMBOL:
|
||
delta = copyin(line, space, ptr, len, ">\n[");
|
||
line += delta;
|
||
ptr += delta;
|
||
space -= delta;
|
||
len -= delta;
|
||
if (space == 0 || len == 0)
|
||
break; /* full line_buff or end of input buffer */
|
||
|
||
if (*ptr != '>') {
|
||
parse_state = PARSING_TEXT;
|
||
break;
|
||
}
|
||
|
||
*line++ = *ptr++; /* copy the '>' */
|
||
space -= 1;
|
||
len -= 1;
|
||
|
||
parse_state = PARSING_SYMEND;
|
||
break;
|
||
|
||
case PARSING_SYMEND:
|
||
if (*ptr != ']') {
|
||
parse_state = PARSING_TEXT; /* not a symbol */
|
||
break;
|
||
}
|
||
|
||
/*
|
||
** It's really a symbol! Replace address with the
|
||
** symbol text.
|
||
*/
|
||
{
|
||
unsigned long value;
|
||
struct symbol sym;
|
||
char *symbol;
|
||
int sym_space;
|
||
|
||
*(line - 1) = 0; /* null terminate the address string */
|
||
value = strtoul(sym_start + 1, NULL, 16);
|
||
*(line - 1) = '>'; /* put back delim */
|
||
|
||
symbol = LookupSymbol(value, &sym);
|
||
if (!symbol_lookup || symbol == NULL) {
|
||
parse_state = PARSING_TEXT;
|
||
break;
|
||
}
|
||
|
||
/*
|
||
** verify there is room in the line buffer
|
||
*/
|
||
sym_space = space + (line - sym_start);
|
||
if (sym_space < strlen(symbol) + 30) { /*(30 should be overkill)*/
|
||
parse_state = PARSING_TEXT; /* not enough space */
|
||
break;
|
||
}
|
||
|
||
delta = sprintf(sym_start, "%s+0x%x/0x%02x]",
|
||
symbol, sym.offset, sym.size);
|
||
|
||
space = sym_space + delta;
|
||
line = sym_start + delta;
|
||
symbols_expanded = 1;
|
||
}
|
||
ptr++;
|
||
len--;
|
||
parse_state = PARSING_TEXT;
|
||
break;
|
||
|
||
default: /* Can't get here! */
|
||
parse_state = PARSING_TEXT;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void LogKernelLine(void)
|
||
{
|
||
int rdcnt;
|
||
|
||
/*
|
||
* Zero-fill the log buffer. This should cure a multitude of
|
||
* problems with klogd logging the tail end of the message buffer
|
||
* which will contain old messages. Then read the kernel log
|
||
* messages into this fresh buffer.
|
||
*/
|
||
memset(log_buffer, '\0', sizeof(log_buffer));
|
||
if ((rdcnt = ksyslog(2, log_buffer, sizeof(log_buffer) - 1)) < 0) {
|
||
if (errno == EINTR)
|
||
return;
|
||
fprintf(stderr,
|
||
"klogd: Error return from sys_sycall: %d - %s\n",
|
||
errno, strerror(errno));
|
||
return;
|
||
}
|
||
|
||
LogLine(log_buffer, rdcnt);
|
||
}
|
||
|
||
static void LogProcLine(void)
|
||
{
|
||
int rdcnt;
|
||
|
||
/*
|
||
* Zero-fill the log buffer. This should cure a multitude of
|
||
* problems with klogd logging the tail end of the message buffer
|
||
* which will contain old messages. Then read the kernel messages
|
||
* from the message pseudo-file into this fresh buffer.
|
||
*/
|
||
memset(log_buffer, '\0', sizeof(log_buffer));
|
||
if ((rdcnt = read(kmsg, log_buffer, sizeof(log_buffer) - 1)) < 0) {
|
||
if (errno == EINTR)
|
||
return;
|
||
Syslog(LOG_ERR, "Cannot read proc file system: %d - %s.",
|
||
errno, strerror(errno));
|
||
return;
|
||
}
|
||
|
||
LogLine(log_buffer, rdcnt);
|
||
}
|
||
|
||
int usage(int code)
|
||
{
|
||
fprintf(stdout,
|
||
"Usage:\n"
|
||
" klogd [-2diInopsvx?] [-c NUM] [-f FILE] [-k FILE]\n"
|
||
"\n"
|
||
"Options:\n"
|
||
" -? Show this help text\n"
|
||
" -2 Print line twice if symbols are successfully expanded\n"
|
||
" -c NUM Set default log level of console messages to NUM (1-8)\n"
|
||
" -d Enable debug mode\n"
|
||
" -f FILE Log messages to FILE rather than the syslog facility\n"
|
||
" -i Signal klogd to reload kernel module symbols\n"
|
||
" -I Signal klogd to reload kernel module *and* static kernel symbols\n"
|
||
" -k FILE Location of kernel symbols (System.map), default: auto\n"
|
||
" -n Run in foreground, required when run from a modern init/supervisor\n"
|
||
" -o Run once, read kernel log messages and syslog them, then exit\n"
|
||
" -p Paranoia mode, forces klogd to reload all kernel symbols on Ooops\n"
|
||
" -s Force use of system call interface to kernel message buffers\n"
|
||
" -v Show program version and exit\n"
|
||
" -x Omit EIP translation, i.e. do not read System.map file\n"
|
||
"\n"
|
||
"Bug report address: %s\n",
|
||
PACKAGE_BUGREPORT);
|
||
exit(code);
|
||
}
|
||
|
||
int main(int argc, char *argv[])
|
||
{
|
||
char *log_level = NULL;
|
||
char *output = NULL;
|
||
int use_output = 0;
|
||
int ch;
|
||
#ifndef TESTING
|
||
pid_t ppid = getpid();
|
||
|
||
chdir("/");
|
||
#endif
|
||
|
||
/* Parse the command-line. */
|
||
while ((ch = getopt(argc, argv, "c:df:iIk:nopsvx2?")) != EOF) {
|
||
switch (ch) {
|
||
case '2': /* Print lines with symbols twice. */
|
||
symbols_twice = 1;
|
||
break;
|
||
|
||
case 'c': /* Set console message level. */
|
||
log_level = optarg;
|
||
break;
|
||
|
||
case 'd': /* Activity debug mode. */
|
||
debugging = 1;
|
||
break;
|
||
|
||
case 'f': /* Define an output file. */
|
||
output = optarg;
|
||
use_output++;
|
||
break;
|
||
|
||
case 'i': /* Reload module symbols. */
|
||
SignalDaemon(SIGUSR1);
|
||
return 0;
|
||
|
||
case 'I':
|
||
SignalDaemon(SIGUSR2);
|
||
return 0;
|
||
|
||
case 'k': /* Kernel symbol file. */
|
||
symfile = optarg;
|
||
break;
|
||
|
||
case 'n': /* don't fork */
|
||
no_fork++;
|
||
break;
|
||
|
||
case 'o': /* One-shot mode. */
|
||
one_shot = 1;
|
||
break;
|
||
|
||
case 'p':
|
||
SetParanoiaLevel(1); /* Load symbols on oops. */
|
||
break;
|
||
|
||
case 's': /* Use syscall interface. */
|
||
use_syscall = 1;
|
||
break;
|
||
|
||
case 'v':
|
||
printf("klogd v%s\n", VERSION);
|
||
exit(1);
|
||
|
||
case 'x':
|
||
symbol_lookup = 0;
|
||
break;
|
||
|
||
case '?':
|
||
usage(0);
|
||
break;
|
||
|
||
default:
|
||
usage(1);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* Set console logging level. */
|
||
if (log_level != NULL) {
|
||
if ((strlen(log_level) > 1) ||
|
||
(strchr("12345678", *log_level) == NULL)) {
|
||
fprintf(stderr, "klogd: Invalid console logging "
|
||
"level <%s> specified.\n",
|
||
log_level);
|
||
return 1;
|
||
}
|
||
console_log_level = *log_level - '0';
|
||
}
|
||
|
||
#ifndef TESTING
|
||
/*
|
||
* The following code allows klogd to auto-background itself.
|
||
* What happens is that the program forks and the parent quits.
|
||
* The child closes all its open file descriptors, and issues a
|
||
* call to setsid to establish itself as an independent session
|
||
* immune from control signals.
|
||
*
|
||
* fork() is only called if it should run in daemon mode, fork is
|
||
* not disabled with the command line argument and there's no
|
||
* such process running.
|
||
*/
|
||
if ((!one_shot) && (!no_fork)) {
|
||
if (!check_pid(PidFile)) {
|
||
signal(SIGTERM, doexit);
|
||
if (fork() == 0) {
|
||
int num_fds = getdtablesize();
|
||
int fl;
|
||
|
||
signal(SIGTERM, SIG_DFL);
|
||
|
||
/* This is the child closing its file descriptors. */
|
||
for (fl = 0; fl <= num_fds; ++fl) {
|
||
if (fileno(stdout) == fl && use_output)
|
||
if (strcmp(output, "-") == 0)
|
||
continue;
|
||
close(fl);
|
||
}
|
||
|
||
setsid();
|
||
} else {
|
||
/*
|
||
* Parent process
|
||
*/
|
||
sleep(300);
|
||
/*
|
||
* Not reached unless something major went wrong.
|
||
*/
|
||
exit(1);
|
||
}
|
||
} else {
|
||
fputs("klogd: Already running.\n", stderr);
|
||
exit(1);
|
||
}
|
||
}
|
||
|
||
/* tuck my process id away */
|
||
if (!check_pid(PidFile)) {
|
||
if (!write_pid(PidFile))
|
||
Terminate();
|
||
} else {
|
||
fputs("klogd: Already running.\n", stderr);
|
||
Terminate();
|
||
}
|
||
#endif
|
||
|
||
/* Signal setups. */
|
||
for (ch = 1; ch < NSIG; ++ch)
|
||
signal(ch, SIG_IGN);
|
||
signal(SIGINT, stop_daemon);
|
||
signal(SIGKILL, stop_daemon);
|
||
signal(SIGTERM, stop_daemon);
|
||
signal(SIGHUP, stop_daemon);
|
||
signal(SIGTSTP, stop_logging);
|
||
signal(SIGCONT, restart);
|
||
signal(SIGUSR1, reload_daemon);
|
||
signal(SIGUSR2, reload_daemon);
|
||
|
||
/* Open outputs. */
|
||
if (use_output) {
|
||
if (strcmp(output, "-") == 0)
|
||
output_file = stdout;
|
||
else if ((output_file = fopen(output, "w")) == NULL) {
|
||
fprintf(stderr, "klogd: Cannot open output file "
|
||
"%s - %s\n",
|
||
output, strerror(errno));
|
||
return 1;
|
||
}
|
||
} else
|
||
openlog("kernel", 0, LOG_KERN);
|
||
|
||
/* Handle one-shot logging. */
|
||
if (one_shot) {
|
||
if (symbol_lookup) {
|
||
InitKsyms(symfile);
|
||
InitMsyms();
|
||
}
|
||
if ((logsrc = GetKernelLogSrc()) == kernel)
|
||
LogKernelLine();
|
||
else
|
||
LogProcLine();
|
||
Terminate();
|
||
}
|
||
|
||
/* Determine where kernel logging information is to come from. */
|
||
#if defined(KLOGD_DELAY)
|
||
sleep(KLOGD_DELAY);
|
||
#endif
|
||
logsrc = GetKernelLogSrc();
|
||
if (symbol_lookup) {
|
||
InitKsyms(symfile);
|
||
InitMsyms();
|
||
}
|
||
|
||
#ifndef TESTING
|
||
if (getpid() != ppid)
|
||
kill(ppid, SIGTERM);
|
||
#endif
|
||
|
||
/* The main loop. */
|
||
while (1) {
|
||
if (change_state)
|
||
ChangeLogging();
|
||
|
||
switch (logsrc) {
|
||
case kernel:
|
||
LogKernelLine();
|
||
break;
|
||
|
||
case proc:
|
||
LogProcLine();
|
||
break;
|
||
|
||
case none:
|
||
pause();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Local Variables:
|
||
* indent-tabs-mode: t
|
||
* c-file-style: "linux"
|
||
* End:
|
||
*/
|